| Document number: | N3719 | |
|---|---|---|
| Date: | 2013-08-17 | |
| Project: | Programming Language C++, Library Working Group | |
| Reply-to: | Tomasz Kamiński <tomaszkam at gmail dot com> |
This proposal is to extend the definition of INVOKE for class member pointers to cover types convertible to a target class of the pointer, like std::reference_wrapper.
Proposal also resolves LWG issue #2219
The standard INVOKE expression models the member pointers as pair of free standing functions, that takes a reference and the pointer (including smart pointers) to target class respectively. However there is difference in semantics between the INVOKE expression for member pointers and functors - for member pointers the conversions are not taken into consideration in matching of first argument.
This behavior difference prohibit uses wrapper types (e.g., std::reference_wrapper, boost::flyweight) in combination with member pointers with the standard library functions that are modeled using INVOKE (e.g., std::bind, std::mem_fn, std::async). The aim of this proposal is to fix that usability problem via extending definition of INVOKE to allow implicit conversions in such situations.
Proposed change will also cover cases of set of convertible types that models the same logical entity (e.g., std::chrono::duration specializations). With the acceptance of this proposal, the std::bind(&std::chrono<double>::count, _1) will create functor returning amount of seconds for any specialization of std::chrono::duration.
operator*The well know workaround for this problem, is to define the operator* that will return the same result as the conversion operator. Firstly this solution is only applicable in situations when the definition of the class can be changed, so it is not feasible for third-party library classes. Secondly it leads inelegant interface than combines wrapper and pointer semantics.
The other workaround is to use the lambda expression instead of library functions, but in the most cases it leads to the less readable code. Please compare following code snippets:
std::bind(&foo, _1, expr, ref(a));
[e = expr, &a] (auto&& arg) -> decltype(auto) { return foo(std::forward<decltype(arg)>(arg), e, a); }
In case of bind expressions the problem may be mitigated by introduction of additional cast functor that preforms required casting.
std::bind(&Class::method, _1)(std::ref(clazz)); std::bind(&Class::method, cast<Class&>(_1))(std::ref(clazz));
However this solution depends on std::is_bind_expression trait and cannot be applied to other library components that depends of INVOKE (e.g., std::async, std::call_once).
Allowing conversion in INVOKE with member pointer may lead to ambiguity in case of entity t, for which both result of t and *t is implicitly convertible to target class of the pointer.
As example, for the following class:
struct Clazz { int foo; }
struct Mixed
{
Clazz& operator*();
operator Clazz&();
};
Mixed m;
The expression INVOKE(&Clazz::foo, m) may be interpreted as static_cast<Clazz>(m).*foo or static_cast<Clazz>(*m).*foo. Existence of such in codebase class may be a result of using work around presented in the motivation section of this proposal.
There are tree possible behaviors in case of such ambiguity:
operator*Raising and ambiguity error will make behaviour of INVOKE for member pointers more uniform with behaviour of free standing functions. However it will break existing code, that uses entities that are both convertible to and behaves as a pointer to target class.
operator*This is the only option that allow extensions of INVOKE definition without breaking or introducing silent behaviour changes in the existing code. The minor drawback is that it leads to more complicated definition of INVOKE.
Preference of the conversion leads to the silent behaviour change of the existing C++11 standard compliant code, so this option should not be considered as a feasible solution.
This proposal recommends implementing the second option and provides the wording in the Proposed wording section. The wording for the first option may be found in the Alternate proposal section. Third option is not further discussed.
This proposal has no dependencies beyond a C++11 compiler and Standard Library implementation. (It depends on perfect forwarding, varidatic templates, decltype and trailing return types.)
Nothing depends on this proposal.
Change the paragraph 20.10.2 Requirements [func.require].
Define
INVOKE(f, t1, t2, ..., tN)as follows:
(t1.*f)(t2, ..., tN)whenfis a pointer to a member function of a classTandt1is an object of typeTor a reference to an object of typeTor a reference to an object of a type derived fromT;((*t1).*f)(t2, ..., tN)whenfis a pointer to a member function of a classTandt1is not one of the types described in the previous item;t1.*fwhenN == 1andfis a pointer to member data of a classTandt1is an object of typeTor a reference to an object of typeTor a reference to an object of a type derived fromT;(*t1).*fwhenN == 1andfis a pointer to member data of a classTandt1is not one of the types described in the previous item;f(t1, t2, ..., tN)in all other cases.
Define viable reference types for member pointer
pof typeM T::*as:
T cv&,T cv&&for all possible cv-qualifierscvifMis not function type,T cv&,T cv&&ifMis function type without ref-qualifier and with cv-qualifierscv,T cv refifMis function type with ref-qualifierrefand cv-qualifierscv.Define
INVOKE(f, t1, t2, ..., tN)as follows:
- when
fis a pointer to a member function of a classTandTRis viable reference type forf:
(t1.*f)(t2, ..., tN)whent1is an object of typeTor a reference to an object of typeTor a reference to an object of a type derived fromT;(TR{*t1}.*f)(t2, ..., tN)whent1does not fulfill criteria of any of previous point and*t1is implicitly convertible toTR;(TR{t1}.*f)(t2, ..., tN)whent1does not fulfill criteria of any of previous point andt1is implicitly convertible toTR;- otherwise expression is ill-formed;
- when
fis a pointer to member data of a classTandN == 1andTRis viable reference type forf:
t1.*fwhent1is an object of typeTor a reference to an object of typeTor a reference to an object of a type derived fromT;TR{*t1}.*fwhent1does not fulfill criteria of any of previous point and*t1is implicitly convertible toTR;TR{t1}.*fwhent1does not fulfill criteria of any of previous point andt1is implicitly convertible toTR;- otherwise expression is ill-formed;
f(t1, t2, ..., tN)in all other cases.
Change the paragraph 20.10.2 Requirements [func.require].
Define
INVOKE(f, t1, t2, ..., tN)as follows:
(t1.*f)(t2, ..., tN)whenfis a pointer to a member function of a classTandt1is an object of typeTor a reference to an object of typeTor a reference to an object of a type derived fromT;((*t1).*f)(t2, ..., tN)whenfis a pointer to a member function of a classTandt1is not one of the types described in the previous item;t1.*fwhenN == 1andfis a pointer to member data of a classTandt1is an object of typeTor a reference to an object of typeTor a reference to an object of a type derived fromT;(*t1).*fwhenN == 1andfis a pointer to member data of a classTandt1is not one of the types described in the previous item;f(t1, t2, ..., tN)in all other cases.
Define viable reference types for member pointer
pof typeM T::*as:
T cv&,T cv&&for all possible cv-qualifierscvifMis not function type,T cv&,T cv&&ifMis function type without ref-qualifier and with cv-qualifierscv,T cv refifMis function type with ref-qualifierrefand cv-qualifierscv.Define
INVOKE(f, t1, t2, ..., tN)as follows:
- when
fis a pointer to a member function of a classTandTRis viable reference type forf:
(TR{t1}.*f)(t2, ..., tN)whent1is implicitly convertible toTR;(TR{*t1}.*f)(t2, ..., tN)when*t1is implicitly convertible toTR;- the expression is ill-formed when neither or both of above points applies;
- when
fis a pointer to member data of a classTandN == 1andTRis viable reference type forf:
TR{t1}.*fwhent1is implicitly convertible toTR;TR{*t1}.*fwhen*t1is implicitly convertible toTR;- the expression is ill-formed when neither or both of above points applies;
f(t1, t2, ..., tN)in all other cases.
Proposed change can be implemented as pure library extension in C++11. Implementation of invoke function that conforms proposed wording can be found https://github.com/tomaszkam/proposals/tree/master/invoke.
Tomasz Miąsko and Mikhail Semenov offered many useful suggestions and corrections to the proposal.
Ville Voutilainen, Gabriel Dos Reis and other people in discussion group ISO C++ Standard - Future Proposals provided numerous insightful suggestions.