Extend defition of INVOKE for member pointers to support types convertible to target class.

233 views
Skip to first unread message

toma...@gmail.com

unread,
Jul 4, 2013, 4:25:16 PM7/4/13
to std-pr...@isocpp.org
The current definition of INVOKE for member pointers to class (lets named it C) doesn't not allow to use as the first arguments types convertible to C as the first argument - the most common example would be the reference wrapper (see LWG Issue #2219). The solution proposed in the issue add supports only to the reference_wrapper, so user defined types with such conversions will still not work (ex. boost::reference_wrapper).

I would like to propose the different  solutions that will cover support for classes converitble to C or references to C. The wording is a bit rough at this point, but I think is clearly stating the intent.

Viable reference types TR for member pointer p of type M T::* are:
  - T&, T&& if M is not function type
  - T cv&, T cv&& if M is function type without ref-qualification and with CV-qualification cv
  - T cv ref if M is function type without ref-qualification ref and with CV-qualification cv

Define INVOKE (f, t1, t2, ..., tN) as follows:
— (static_cast<TR>(t1).*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is convertible to TR where TR is viable reference type for f.
— (static_cast<TR>(*t1).*f)(t2, ..., tN) when f is a pointer to a member function of a class T and *t1 is convertible to TR where TR is viable reference type for f.
— static_cast<TR>(t1).*f when N == 1 and f is a pointer to member data of a class T and t1 is convertible to TR where TR is viable reference type for f.
— static_cast<TR>(*t1).*f when N == 1 and f is a pointer to member data of a class Tand t1 is convertible to TR where TR is viable reference type for f.
— f(t1, t2, ..., tN) in all other cases.

The should be additional statement that defines that if for member pointer, more than one options matches, then INVOKE should be ill-formed. No preference. Also point about conversion to TR covers references to T and its deliver classes. I don't really know how to define it.

If the new definition will be accepted then, the behavior of the INVOKE will change for the classes that defines both operator* returning class C and having conversion operator to C, example:
struct weird
{
  C& operator*();
  operator C();
};

But for this cases the problem wont compile, so now silent behavior changes would be introduced.

In the attachment I included proof-of-concept implementation of invoke function.
invoke.cpp

Ville Voutilainen

unread,
Jul 4, 2013, 6:01:17 PM7/4/13
to std-pr...@isocpp.org
On 4 July 2013 23:25, <toma...@gmail.com> wrote:
The current definition of INVOKE for member pointers to class (lets named it C) doesn't not allow to use as the first arguments types convertible to C as the first argument - the most common example would be the reference wrapper (see LWG Issue #2219). The solution proposed in the issue add supports only to the reference_wrapper, so user defined types with such conversions will still not work (ex. boost::reference_wrapper).

I would like to propose the different  solutions that will cover support for classes converitble to C or references to C. The wording is a bit rough at this point, but I think is clearly stating the intent.

Viable reference types TR for member pointer p of type M T::* are:
  - T&, T&& if M is not function type
  - T cv&, T cv&& if M is function type without ref-qualification and with CV-qualification cv
  - T cv ref if M is function type without ref-qualification ref and with CV-qualification cv

The last one should say "with ref-qualification", not "without ref-qualification".
 

Define INVOKE (f, t1, t2, ..., tN) as follows:
— (static_cast<TR>(t1).*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is convertible to TR where TR is viable reference type for f.

I wonder how you plan to prevent these from downcasting in a class hierarchy. This wording won't do it, and I can't
see such prevention in the proof-of-concept implementation either.

If the new definition will be accepted then, the behavior of the INVOKE will change for the classes that defines both operator* returning class C and having conversion operator to C, example:
struct weird
{
  C& operator*();
  operator C();
};

But for this cases the problem wont compile, so now silent behavior changes would be introduced.

I don't find such a class weird. Does the behavior change so that things that used to be valid are no longer
valid, then? Is this weird-struct ok for INVOKE under the current rules, but invalid under your proposal?


toma...@gmail.com

unread,
Jul 4, 2013, 6:33:26 PM7/4/13
to std-pr...@isocpp.org
Firstly, in the wording the I improperly specified the viable references for member object pointer type (missing cv):
Viable reference types TR for member pointer p of type M T::* are:
  - T cv&, T cv&& if M is not function type for all possible CV-qualifications

  - T cv&, T cv&& if M is function type without ref-qualification and with CV-qualification cv
  - T cv ref if M is function type with ref-qualification ref and with CV-qualification cv


Define INVOKE (f, t1, t2, ..., tN) as follows:
— (static_cast<TR>(t1).*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is convertible to TR where TR is viable reference type for f.
— (static_cast<TR>(*t1).*f)(t2, ..., tN) when f is a pointer to a member function of a class T and *t1 is convertible to TR where TR is viable reference type for f.
— static_cast<TR>(t1).*f when N == 1 and f is a pointer to member data of a class T and t1 is convertible to TR where TR is viable reference type for f.
— static_cast<TR>(*t1).*f when N == 1 and f is a pointer to member data of a class Tand t1 is convertible to TR where TR is viable reference type for f.
— f(t1, t2, ..., tN) in all other cases.

Also I attach changed implementation of proof-of-concept.



W dniu piątek, 5 lipca 2013 00:01:17 UTC+2 użytkownik Ville Voutilainen napisał:

Viable reference types TR for member pointer p of type M T::* are:
  - T&, T&& if M is not function type
  - T cv&, T cv&& if M is function type without ref-qualification and with CV-qualification cv
  - T cv ref if M is function type without ref-qualification ref and with CV-qualification cv

The last one should say "with ref-qualification", not "without ref-qualification".
 
Ok.
 

Define INVOKE (f, t1, t2, ..., tN) as follows:
— (static_cast<TR>(t1).*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is convertible to TR where TR is viable reference type for f.

In the case of downcast t1 (being a base class of T) wouldn't be convertible (implictly) to any of TR. The implementation do the same things.
 

If the new definition will be accepted then, the behavior of the INVOKE will change for the classes that defines both operator* returning class C and having conversion operator to C, example:
struct weird
{
  C& operator*();
  operator C();
};

But for this cases the problem wont compile, so now silent behavior changes would be introduced.

I don't find such a class weird. Does the behavior change so that things that used to be valid are no longer
valid, then? Is this weird-struct ok for INVOKE under the current rules, but invalid under your proposal?

For current wording this kind of classes will go trough the (*t).*f path. In my proposed implementation there will be dis-ambiguity between path using conversion operator and the path going via operator*, so it will be no longer valid. Also I find such weird, because they merge the pointer-to-C semantics with a obejct-of-type-C semantics, so in my opinion there is nothing wrong to make them ambiguous in cases of calling member function of C.
 
invoke.cpp

Ville Voutilainen

unread,
Jul 4, 2013, 6:52:31 PM7/4/13
to std-pr...@isocpp.org
On 5 July 2013 01:33, <toma...@gmail.com> wrote:


Define INVOKE (f, t1, t2, ..., tN) as follows:
— (static_cast<TR>(t1).*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is convertible to TR where TR is viable reference type for f.

In the case of downcast t1 (being a base class of T) wouldn't be convertible (implictly) to any of TR. The implementation do the same things.

Thanks. I am not well-versed with INVOKE, it would be helpful if it said somewhere that the conversion must be
implicit. Specifying it in terms of static_cast suggests otherwise, so perhaps that could be said directly
in the wording?


For current wording this kind of classes will go trough the (*t).*f path. In my proposed implementation there will be dis-ambiguity between path using conversion operator and the path going via operator*, so it will be no longer valid. Also I find such weird, because they merge the pointer-to-C semantics with a obejct-of-type-C semantics, so in my opinion there is nothing wrong to make them ambiguous in cases of calling member function of C.



Well, I think going through the (*t).*f path makes a lot of sense, compared to going via the conversion operator
which makes no sense to me, because it creates a temporary anyway. Having such a nonsense conversion
be ambiguous with the operator* thus doesn't make much sense to me either. In other words, I have a lot
of preference for operator* being preferred to conversion functions. I also happen to think that having this
conversion support is worth breaking that kind of classes, because as I said, I don't find them weird. The
standard may not include any such classes, but I have seen such code in the wild.

Ville Voutilainen

unread,
Jul 4, 2013, 7:02:40 PM7/4/13
to std-pr...@isocpp.org
On 5 July 2013 01:52, Ville Voutilainen <ville.vo...@gmail.com> wrote:
of preference for operator* being preferred to conversion functions. I also happen to think that having this
conversion support is worth breaking that kind of classes, because as I said, I don't find them weird. The



That bit is missing a "not". :) I do NOT think having this convertibility support is worth the breakage,
so I think it would be beneficial to avoid the breakage to gain this convertibility support. 

Message has been deleted

Gabriel Dos Reis

unread,
Jul 4, 2013, 7:08:22 PM7/4/13
to std-pr...@isocpp.org
Ville Voutilainen <ville.vo...@gmail.com> writes:

[...]

| If the new definition will be accepted then, the behavior of the INVOKE
| will change for the classes that defines both operator* returning class C
| and having conversion operator to C, example:
| struct weird
| {
|   C& operator*();
|   operator C();
| };
| But for this cases the problem wont compile, so now silent behavior changes
| would be introduced.
|
|
| I don't find such a class weird.

Indeed, it isn't. It isn't far from a wrapper class that turns an
integer into an iterator.

-- Gaby

toma...@gmail.com

unread,
Jul 4, 2013, 7:18:28 PM7/4/13
to std-pr...@isocpp.org


W dniu piątek, 5 lipca 2013 00:52:31 UTC+2 użytkownik Ville Voutilainen napisał:



On 5 July 2013 01:33, <toma...@gmail.com> wrote:


Define INVOKE (f, t1, t2, ..., tN) as follows:
— (static_cast<TR>(t1).*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is convertible to TR where TR is viable reference type for f.

In the case of downcast t1 (being a base class of T) wouldn't be convertible (implictly) to any of TR. The implementation do the same things.

Thanks. I am not well-versed with INVOKE, it would be helpful if it said somewhere that the conversion must be
implicit. Specifying it in terms of static_cast suggests otherwise, so perhaps that could be said directly
in the wording?

To be clear. That was my intent when I was making this wording and that is how my implementation works.
 
For current wording this kind of classes will go trough the (*t).*f path. In my proposed implementation there will be dis-ambiguity between path using conversion operator and the path going via operator*, so it will be no longer valid. Also I find such weird, because they merge the pointer-to-C semantics with a obejct-of-type-C semantics, so in my opinion there is nothing wrong to make them ambiguous in cases of calling member function of C.



Well, I think going through the (*t).*f path makes a lot of sense, compared to going via the conversion operator
which makes no sense to me, because it creates a temporary anyway. Having such a nonsense conversion
be ambiguous with the operator* thus doesn't make much sense to me either. In other words, I have a lot
of preference for operator* being preferred to conversion functions. I also happen to think that having this
conversion support is worth breaking that kind of classes, because as I said, I don't find them weird. The
standard may not include any such classes, but I have seen such code in the wild.

Firstly, consider the following function:
struct weird2
{
  C& operator*();
  operator C&();
};

Is there any reason to prefer operator* over conversion operator? For my point of view there is not.

Secondly, consider the following code:
void free_standing(const C&);

free_standing(weird()); //ups, I forgot to invoke *
free_standing(weird2());
In this case the conversion operator will be invoked instead of operator*, so I my personal preference would be to preffer conversions over operator* (as will free-standing functions do). That difference in personal taste convince me that should cases should end up with compilation error and ambiguity (no silent changes). Secondly using such classes may ends with some non-trivial problems, as the one I tried to present with free standing function example.
Message has been deleted

toma...@gmail.com

unread,
Jul 4, 2013, 7:22:41 PM7/4/13
to std-pr...@isocpp.org, g...@axiomatics.org


Why would you need a conversion to int operator in addition to operator* that returns in int in such wrapper?
 

Ville Voutilainen

unread,
Jul 4, 2013, 7:43:10 PM7/4/13
to std-pr...@isocpp.org
On 5 July 2013 02:18, <toma...@gmail.com> wrote:
In the case of downcast t1 (being a base class of T) wouldn't be convertible (implictly) to any of TR. The implementation do the same things.

Thanks. I am not well-versed with INVOKE, it would be helpful if it said somewhere that the conversion must be
implicit. Specifying it in terms of static_cast suggests otherwise, so perhaps that could be said directly
in the wording?

To be clear. That was my intent when I was making this wording and that is how my implementation works.

I recommend testing that the implementation actually does that, then, and changing the wording to say that
it really requires implicit convertibility.
 

Well, I think going through the (*t).*f path makes a lot of sense, compared to going via the conversion operator
which makes no sense to me, because it creates a temporary anyway. Having such a nonsense conversion
be ambiguous with the operator* thus doesn't make much sense to me either. In other words, I have a lot
of preference for operator* being preferred to conversion functions. I also happen to think that having this
conversion support is worth breaking that kind of classes, because as I said, I don't find them weird. The
standard may not include any such classes, but I have seen such code in the wild.

Firstly, consider the following function:
struct weird2
{
  C& operator*();
  operator C&();
};

Is there any reason to prefer operator* over conversion operator? For my point of view there is not.

No, but it does no harm, and gives a sane result. Doing the opposite does no harm either in this
case, but it does harm in the case of the original "struct weird". Prefering operator* seems sane
in all cases. Perhaps that's why the current INVOKE does it.

 

Secondly, consider the following code:
void free_standing(const C&);

free_standing(weird()); //ups, I forgot to invoke *
free_standing(weird2());
In this case the conversion operator will be invoked instead of operator*, so I my personal preference would be to preffer conversions over operator* (as will free-standing functions do). That difference in personal taste convince me that should cases

INVOKE doesn't model just free-standing functions. It has to make some compromises to support the variety of things
it supports, one of those compromises being that it can't model free-standing functions exactly.

Ville Voutilainen

unread,
Jul 4, 2013, 7:44:30 PM7/4/13
to std-pr...@isocpp.org, Gabriel Dos Reis
On 5 July 2013 02:22, <toma...@gmail.com> wrote:

Indeed, it isn't.  It isn't far from a wrapper class that turns an
integer into an iterator.

-- Gaby


Why would you need a conversion to int operator in addition to operator* that returns in int in such wrapper?


In order to allow the wrapper type to behave both like an int (in a limited fashion) and like an iterator.

Gabriel Dos Reis

unread,
Jul 4, 2013, 8:48:31 PM7/4/13
to toma...@gmail.com, std-pr...@isocpp.org
toma...@gmail.com writes:

| | struct weird
| | {
| | C& operator*();
| | operator C();
| | };
| | But for this cases the problem wont compile, so now silent behavior
| changes
| | would be introduced.
| |
| |
| | I don't find such a class weird.
|
| Indeed, it isn't. It isn't far from a wrapper class that turns an
| integer into an iterator.
|
| -- Gaby
|
|
| Why would you need a conversion to int operator in addition to operator* that
| retunrs int and int in such wrapper? I probably miss some use cases.

It is a wrapper around integer, shouldn't it have that conversion?

-- Gaby

toma...@gmail.com

unread,
Jul 5, 2013, 4:34:18 AM7/5/13
to std-pr...@isocpp.org, toma...@gmail.com, g...@axiomatics.org


W dniu piątek, 5 lipca 2013 01:43:10 UTC+2 użytkownik Ville Voutilainen napisał:

I recommend testing that the implementation actually does that, then, and changing the wording to say that
it really requires implicit convertibility.
 
It was tested against that case.
 

No, but it does no harm, and gives a sane result. Doing the opposite does no harm either in this
case, but it does harm in the case of the original "struct weird". Prefering operator* seems sane
in all cases. Perhaps that's why the current INVOKE does it.

I would harm in case of:
struct weird3
{
  C operator*();
  operator C&();
};
 
 
INVOKE doesn't model just free-standing functions. It has to make some compromises to support the variety of things
it supports, one of those compromises being that it can't model free-standing functions exactly.


For my perspective it does. I see the mem_fn as converting the member function M (Class::*)(Args...) cv ref to be invokable as free standing-function M (Class cv ref, Args...) (if ref is missing there should be & and && versions), exactly the same as it used for doing an overload resolution. In addition it supports  pointers to Class and pointer-like types.


W dniu piątek, 5 lipca 2013 02:48:31 UTC+2 użytkownik Gabriel Dos Reis napisał:
It is a wrapper around integer, shouldn't it have that conversion?

I don't see a point to have that class to act both as a integer and an iterator. I see it would be pretty useful in case of STL algorithms and container, but they will use only operator* to extract the value, not the conversions operator. I don't see a point in using it in the context when the simple integer will be enough. Maybe I miss something.

Ville Voutilainen

unread,
Jul 5, 2013, 7:24:30 AM7/5/13
to std-pr...@isocpp.org
On 5 July 2013 11:34, <toma...@gmail.com> wrote:


W dniu piątek, 5 lipca 2013 01:43:10 UTC+2 użytkownik Ville Voutilainen napisał:

I recommend testing that the implementation actually does that, then, and changing the wording to say that
it really requires implicit convertibility.
 
It was tested against that case.

I didn't find such a test in the proof-of-concept code.
 
 

No, but it does no harm, and gives a sane result. Doing the opposite does no harm either in this
case, but it does harm in the case of the original "struct weird". Prefering operator* seems sane
in all cases. Perhaps that's why the current INVOKE does it.

I would harm in case of:
struct weird3
{
  C operator*();
  operator C&();
};

Now, this actually _is_ a weird type, since it returns a value from its operator*. If that value happens
to be a proxy value that does the right thing, that's fine - otherwise it will shoot the user in the foot
in other areas beyond INVOKE, and it's not INVOKE's job to fix that problem. I see the reasoning
for making such cases ambiguous, but if they aren't ambiguous currently, I'd want very strong
reasons to introduce potential breakage to existing code, when we can avoid it.
 
 
 
INVOKE doesn't model just free-standing functions. It has to make some compromises to support the variety of things
it supports, one of those compromises being that it can't model free-standing functions exactly.


For my perspective it does. I see the mem_fn as converting the member function M (Class::*)(Args...) cv ref to be invokable as free standing-function M (Class cv ref, Args...) (if ref is missing there should be & and && versions), exactly the same as it used for doing an overload resolution. In addition it supports  pointers to Class and pointer-like types.

I may have used imprecise terminology. INVOKE certainly unifies target types so that their invocations become
similar to free functions. However, that doesn't necessarily mean INVOKE should behave as if all its targets
were free functions, because they aren't.

My recommendation is still to avoid introducing the ambiguity. Otherwise, the proposal seems good and desirable,
so I'd recommend putting it forward to LEWG. In other words, nice job so far!

Gabriel Dos Reis

unread,
Jul 5, 2013, 7:38:49 AM7/5/13
to toma...@gmail.com, std-pr...@isocpp.org
toma...@gmail.com writes:

[...]

| W dniu piątek, 5 lipca 2013 02:48:31 UTC+2 użytkownik Gabriel Dos Reis napisał:
|
| It is a wrapper around integer, shouldn't it have that conversion?
|
|
| I don't see a point to have that class to act both as a integer and an
| iterator. I see it would be pretty useful in case of STL algorithms and
| container, but they will use only operator* to extract the value, not the
| conversions operator. I don't see a point in using it in the context when the
| simple integer will be enough. Maybe I miss something.

Well, just because one doesn't see the point means it is worth breaking.
It would be devastating if one was to use lack of vision as license to
break stuff. Even though we can't forsee every development, we usually
try hard.

STL principles do not require that a type not provide additional
operations if it already satisfies a certain category of iterator
requirements. In fact, integers form one of the most abstract
representations of iterators. The whole notion of iterators as you see
them in STL is based on Peano algebra, and a few additional axioms
concerning computational complexity.

-- Gaby

Gabriel Dos Reis

unread,
Jul 5, 2013, 7:41:27 AM7/5/13
to std-pr...@isocpp.org
Ville Voutilainen <ville.vo...@gmail.com> writes:

| struct weird3
| {
|   C operator*();
|   operator C&();
| };
|
|
| Now, this actually _is_ a weird type, since it returns a value from its
| operator*.

even vector<bool>::reference doesn't do this.

-- Gaby

toma...@gmail.com

unread,
Jul 5, 2013, 8:35:33 AM7/5/13
to std-pr...@isocpp.org
To avoid breakage of classes that has both operator* and conversion operator to class reference the wording may be changed in the following way:
Viable reference types TR for member pointer p of type M T::* are:
  - T cv&, T cv && for all posible CV-qualifications if M is not function type

  - T cv&, T cv&& if M is function type without ref-qualification and with CV-qualification cv
  - T cv ref if M is function type with ref-qualification ref and with CV-qualification cv


Define INVOKE (f, t1, t2, ..., tN) as follows:
- (t1.*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is an object of
   type T or a reference to an object of type T or a reference to an object of a type derived from T;
— (static_cast<TR>(*t1).*f)(t2, ..., tN) when f is a pointer to a member function of a class T and
  t1 does not fulfill
criteria of any of previous point and *t1 is convertible to TR where TR is viable
  reference type for f.
— (static_cast<TR>(t1).*f)(t2, ..., tN) when f is a pointer to a member function of a class T and
  t1 does not fulfill criteria of any of previous point and *t1 is convertible to TR where TR is viable
  reference type for f.
— t1.*f when N == 1 and f is a pointer to member data of a class T and t1 is an object of type T or a
  reference to an object of type T or a reference to an object of a type derived from T;
— static_cast<TR>(*t1).*f when N == 1 and f is a pointer to member data of a class T and
 
t1 does not fulfill criteria and *t1 is convertibleto TR where TR is viable reference type for f.
- static_cast<TR>(t1).*f when N == 1 and f is a pointer to member data of a class T and
 
t1 does not fulfill criteria t1 is convertible to TR where TR is viable reference type for f.

— f(t1, t2, ..., tN) in all other cases.

This wording would prefer use of operator* over the conversions operator if both exists in the classes, so there will be not functionality change with this addition. I am pretty sure that that approach is implementable,  but I am not sure if I would like to propose to go that direction (see comments bellow).


W dniu piątek, 5 lipca 2013 13:24:30 UTC+2 użytkownik Ville Voutilainen napisał:

For my perspective it does. I see the mem_fn as converting the member function M (Class::*)(Args...) cv ref to be invokable as free standing-function M (Class cv ref, Args...) (if ref is missing there should be & and && versions), exactly the same as it used for doing an overload resolution. In addition it supports  pointers to Class and pointer-like types.

I may have used imprecise terminology. INVOKE certainly unifies target types so that their invocations become
similar to free functions. However, that doesn't necessarily mean INVOKE should behave as if all its targets
were free functions, because they aren't.

But I would like them, to model this functions as close as possible to the free standing functions, so I would rather see prefenrecen of conversion operator over operator*.  But that is the case of the presonal taste, so I think that I would be the best to come out with the solution that will choose some default behavior for such classes (probably the going trought operator* as it is not breaking) but allow to change it, but at this moment I don't see a clean way to do it.


W dniu piątek, 5 lipca 2013 13:38:49 UTC+2 użytkownik Gabriel Dos Reis napisał:
Well, just because one doesn't see the point means it is worth breaking.
It would be devastating if one was to use lack of vision as license to
break stuff.  Even though we can't forsee every development, we usually
try hard.


Ok, I got your point. The main problem is that standard does not forsee the usage of INVOKE with a types with only conversion operator to class type (like std::reference_wrapper).

Ville Voutilainen

unread,
Jul 5, 2013, 8:40:07 AM7/5/13
to std-pr...@isocpp.org
On 5 July 2013 15:35, <toma...@gmail.com> wrote:



But I would like them, to model this functions as close as possible to the free standing functions, so I would rather see prefenrecen of conversion operator over operator*.  But that is the case of the presonal taste, so I think that I would be the best to come out with the solution that will choose some default behavior for such classes (probably the going trought operator* as it is not breaking) but allow to change it, but at this moment I don't see a clean way to do it.



It's not just a matter of taste. The apparently current preference of operator* (even if as a result of not considering
conversions functions at all) is the status quo in a released standard. Breaking those semantics should not
be done lightly, even if some personal preference suggests otherwise.

toma...@gmail.com

unread,
Jul 5, 2013, 9:01:13 AM7/5/13
to std-pr...@isocpp.org
 
Yes, you are right, I want to say that this will be matter of test if there will be no standard rules (I will be in ambiguity fraction then). The main problem that I didn't notice before, I that I am breaking the existing code (which I would be rather ok with - just may opinion, for me the must have it to not introduce silent behavior changes) without providing any work-around for the situations when the problem occur .
 

Jonathan Wakely

unread,
Jul 5, 2013, 12:47:13 PM7/5/13
to std-pr...@isocpp.org
On Friday, July 5, 2013 1:40:07 PM UTC+1, Ville Voutilainen wrote:
It's not just a matter of taste. The apparently current preference of operator* (even if as a result of not considering
conversions functions at all) is the status quo in a released standard. Breaking those semantics should not
be done lightly, even if some personal preference suggests otherwise.


Making *any* changes to the INVOKE semantics should not be done lightly, it's seriously tricky stuff!

I'd like to see some more convincing motivation for this change.  std::reference_wrapper is a good motivating case, but I'm less sure about boost::reference_wrapper ... do you really need another reference_wrapper if you have a C++11 implementation?  (I'm aware that the boost one allows incomplete types.)   Such wrapper types can always be made to work with INVOKE by providing operator* so the last bullet of INVOKE handles them, although that's a bit smelly, I admit.

Ville Voutilainen

unread,
Jul 5, 2013, 1:43:30 PM7/5/13
to std-pr...@isocpp.org
I would find using a wrapper lambda a less smelly alternative. For people who'd rather not write such things,
this proposal seems quite useful to me - and I expect that types that would take advantage of it are probably
not types in the standard library or in boost.

This will likely change certain SFINAE results, but I expect those to be non-breaking, and ultimately
this would allow people to avoid having to SFINAE that kind of cases. Overall, this looks like something
the LEWG should probably take a look at.

Daniel Krügler

unread,
Jul 5, 2013, 2:29:37 PM7/5/13
to std-pr...@isocpp.org
2013/7/5 Jonathan Wakely <c...@kayari.org>:
> I'd like to see some more convincing motivation for this change.
> std::reference_wrapper is a good motivating case, but I'm less sure about
> boost::reference_wrapper ... do you really need another reference_wrapper if
> you have a C++11 implementation? (I'm aware that the boost one allows
> incomplete types.)

Btw.: Peter Dimov recently asked on the library reflector whether
std::reference_wrapper shouldn't give up the special member type
protocol to also allow for incomplete types.

- Daniel
Message has been deleted

toma...@gmail.com

unread,
Jul 5, 2013, 5:18:52 PM7/5/13
to std-pr...@isocpp.org
I have manage to do proof-of-concept implementation of invoke function compatible with the changed wording. I think it is pretty well self documenting, but should be red in button-up manner (from main).

The corrected version of the wording.
Viable reference types TR for member pointer p of type M T::* are:
  - T cv&, T cv && for all posible CV-qualifications if M is not function type
  - T cv&, T cv&& if M is function type without ref-qualification and with CV-qualification cv
  - T cv ref if M is function type with ref-qualification ref and with CV-qualification cv

Define INVOKE (f, t1, t2, ..., tN) as follows:
- (t1.*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is an object of
   type T or a reference to an object of type T or a reference to an object of a type derived from T;
— (static_cast<TR>(*t1).*f)(t2, ..., tN) when f is a pointer to a member function of a class T and
  t1 does not fulfill criteria of any of previous point and *t1 is implicitly convertible to TR where
  TR is viable reference type for f.
— (static_cast<TR>(t1).*f)(t2, ..., tN) when f is a pointer to a member function of a class T and
  t1 does not fulfill criteria of any of previous point and t1 is implicitly convertible to TR where
  TR is viable reference type for f.
— t1.*f when N == 1 and f is a pointer to member data of a class T and t1 is an object of type T or a
  reference to an object of type T or a reference to an object of a type derived from T;
— static_cast<TR>(*t1).*f when N == 1 and f is a pointer to member data of a class T and
  t1 does not fulfill criteria of any of previous point and *t1 is implicitly convertible to TR where

  TR is viable reference type for
f.
- static_cast<TR>(t1).*f when N == 1 and f is a pointer to member data of a class T and
  t1 does not fulfill criteria of any of previous point and t1 is implicitly convertible to TR where

  TR is viable reference type for f.
— f(t1, t2, ..., tN) in all other cases.
I would appreciate any suggestions for the wording improvements.


2013/7/5 Jonathan Wakely <c...@kayari.org>:
> I'd like to see some more convincing motivation for this change.
> std::reference_wrapper is a good motivating case, but I'm less sure about
> boost::reference_wrapper ... do you really need another reference_wrapper if
> you have a C++11 implementation?  (I'm aware that the boost one allows
> incomplete types.)

 The proposed wording does support any wrapper with appropriate conversion operator, so it would support any user defined wrapper class. From the other side, I  do not think that std::reference_wrapper covers all possible usages of such wrappers, but at this point nothing come to my mind, except the int wrapped into iterator (but int does not have member methods). Any suggestions?
 
invoke_without_breaking.cpp

toma...@gmail.com

unread,
Jul 9, 2013, 8:58:05 AM7/9/13
to std-pr...@isocpp.org


On Friday, July 5, 2013 6:47:13 PM UTC+2, Jonathan Wakely wrote:

Making *any* changes to the INVOKE semantics should not be done lightly, it's seriously tricky stuff!

I'd like to see some more convincing motivation for this change.  std::reference_wrapper is a good motivating case, but I'm less sure about boost::reference_wrapper ... do you really need another reference_wrapper if you have a C++11 implementation?  (I'm aware that the boost one allows incomplete types.)   Such wrapper types can always be made to work with INVOKE by providing operator* so the last bullet of INVOKE handles them, although that's a bit smelly, I admit.

Other types that will be addressed by this proposal:
   1. boost::flyweight<T> - similiar to reference_wrapper
   2. std::chrono::duration - std::mem_fn(&std::chrono::duration<double>::count) will work now with any duration specialization
For these types the operator* cannot be defined.

toma...@gmail.com

unread,
Jul 18, 2013, 2:20:46 PM7/18/13
to std-pr...@isocpp.org
I the attachment you may found the proposal to changed the definition of INVOKE to support convertible types (also it can be found on github). I will appreciate any feedback on the wording, before I will send it to assign document number.

On the Alernate proposal - in included this section, because in design decision I pointed out simpler wording and from my perspective such statement requires putting actual wording to be compared and assessed by readers.
Extend INVOKE to support types convertible to target class.html

toma...@gmail.com

unread,
Aug 17, 2013, 11:07:44 AM8/17/13
to std-pr...@isocpp.org
I have changed occurrences of static_cast<TR>(expr) to TR{expr}, to avoid unnecessary suggestions that down-cast can be performed by proposed invoke.
Extend INVOKE to support types convertible to target class.html
Reply all
Reply to author
Forward
0 new messages