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)
whenf
is a pointer to a member function of a classT
andt1
is an object of typeT
or a reference to an object of typeT
or a reference to an object of a type derived fromT
;((*t1).*f)(t2, ..., tN)
whenf
is a pointer to a member function of a classT
andt1
is not one of the types described in the previous item;t1.*f
whenN == 1
andf
is a pointer to member data of a classT
andt1
is an object of typeT
or a reference to an object of typeT
or a reference to an object of a type derived fromT
;(*t1).*f
whenN == 1
andf
is a pointer to member data of a classT
andt1
is 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
p
of typeM T::*
as:
T cv&
,T cv&&
for all possible cv-qualifierscv
ifM
is not function type,T cv&
,T cv&&
ifM
is function type without ref-qualifier and with cv-qualifierscv
,T cv ref
ifM
is function type with ref-qualifierref
and cv-qualifierscv
.Define
INVOKE(f, t1, t2, ..., tN)
as follows:
- when
f
is a pointer to a member function of a classT
andTR
is viable reference type forf
:
(t1.*f)(t2, ..., tN)
whent1
is an object of typeT
or a reference to an object of typeT
or a reference to an object of a type derived fromT
;(TR{*t1}.*f)(t2, ..., tN)
whent1
does not fulfill criteria of any of previous point and*t1
is implicitly convertible toTR
;(TR{t1}.*f)(t2, ..., tN)
whent1
does not fulfill criteria of any of previous point andt1
is implicitly convertible toTR
;- otherwise expression is ill-formed;
- when
f
is a pointer to member data of a classT
andN == 1
andTR
is viable reference type forf
:
t1.*f
whent1
is an object of typeT
or a reference to an object of typeT
or a reference to an object of a type derived fromT
;TR{*t1}.*f
whent1
does not fulfill criteria of any of previous point and*t1
is implicitly convertible toTR
;TR{t1}.*f
whent1
does not fulfill criteria of any of previous point andt1
is 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)
whenf
is a pointer to a member function of a classT
andt1
is an object of typeT
or a reference to an object of typeT
or a reference to an object of a type derived fromT
;((*t1).*f)(t2, ..., tN)
whenf
is a pointer to a member function of a classT
andt1
is not one of the types described in the previous item;t1.*f
whenN == 1
andf
is a pointer to member data of a classT
andt1
is an object of typeT
or a reference to an object of typeT
or a reference to an object of a type derived fromT
;(*t1).*f
whenN == 1
andf
is a pointer to member data of a classT
andt1
is 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
p
of typeM T::*
as:
T cv&
,T cv&&
for all possible cv-qualifierscv
ifM
is not function type,T cv&
,T cv&&
ifM
is function type without ref-qualifier and with cv-qualifierscv
,T cv ref
ifM
is function type with ref-qualifierref
and cv-qualifierscv
.Define
INVOKE(f, t1, t2, ..., tN)
as follows:
- when
f
is a pointer to a member function of a classT
andTR
is viable reference type forf
:
(TR{t1}.*f)(t2, ..., tN)
whent1
is implicitly convertible toTR
;(TR{*t1}.*f)(t2, ..., tN)
when*t1
is implicitly convertible toTR
;- the expression is ill-formed when neither or both of above points applies;
- when
f
is a pointer to member data of a classT
andN == 1
andTR
is viable reference type forf
:
TR{t1}.*f
whent1
is implicitly convertible toTR
;TR{*t1}.*f
when*t1
is 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.