2219. INVOKE-ing a pointer to member with a reference_wrapper as the object expression

Section: 23.14.3 [func.require] Status: C++17 Submitter: Jonathan Wakely Opened: 2012-11-28 Last modified: 2017-07-30

Priority: 2

View all other issues in [func.require].

View all issues with C++17 status.

Discussion:

The standard currently requires this to be invalid:

#include <functional>

struct X { int i; } x;
auto f = &X::i;
auto t1 = std::ref(x);
int i = std::mem_fn(f)(t1);

The call expression on the last line is equivalent to INVOKE(f, std::ref(x)) which according to 23.14.3 [func.require]p1 results in the invalid expression (*t1).*f because reference_wrapper<X> is neither an object of type X nor a reference to an object of type X nor a reference to an object of a type derived from X.

The same argument applies to pointers to member functions, and if they don't work with INVOKE it becomes harder to do all sorts of things such as:

call_once(o, &std::thread::join, std::ref(thr))

or

async(&std::list<int>::sort, std::ref(list));

The definition of INVOKE should be extended to handle reference wrappers.

[2013-03-15 Issues Teleconference]

Moved to Review.

The wording seems accurate, but verbose. If possible, we would like to define the kind of thing being specified so carefully as one of a number of potential language constructs in a single place. It is also possible that this clause is that single place.

[2013-04-18, Bristol]

Jonathan comments:

In the proposed resolution in the first bullet (t1.*f) is not valid if t1 is a reference_wrapper, so we probably need a separate bullet to handle the reference_wrapper case.

[2014-02-14, Issaquah, Mike Spertus supplies wording]

Previous resolution from Jonathan [SUPERSEDED]:

This wording is relative to N3485.

  1. Edit 23.14.3 [func.require]:

    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 U or an object of type reference_wrapper<U> or a reference to an object of type reference_wrapper<U> where U is either the type T or a type derived from T;

    • ((*t1).*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is not one of the types described in the previous item;

    • 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 U or an object of type reference_wrapper<U> or a reference to an object of type reference_wrapper<U> where U is either the type T or a type derived from T;

    • (*t1).*f when N == 1 and f is a pointer to member data of a class T and t1 is not one of the types described in the previous item;

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

[2014-10-01, STL adds discussion and provides an improved resolution]

Because neither t1.*f nor (*t1).*f will compile when t1 is reference_wrapper<U> for any U, we don't need to inspect U carefully. We can bluntly detect all reference_wrappers and use get() for them.

We would have to be more careful if we had to deal with pointers to members of reference_wrapper itself. Fortunately, we don't. First, it doesn't have user-visible data members. Second, users technically can't take the addresses of its member functions (this is a consequence of 20.5.5.5 [member.functions], the Implementer's Best Friend).

While we're in the neighborhood, I recommend simplifying and clarifying the wording used to detect base/derived objects.

Previous resolution from Mike Spertus [SUPERSEDED]:

This wording is relative to N3936.

  1. Edit 23.14.3 [func.require]:

    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;

    • (t1.get().*f)(t2, ..., tN) when f is a pointer to a member function of class T and t1 is an object of type reference_wrapper<U> where U is either the type T or a type derived from T.

    • ((*t1).*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is not one of the types described in the previous item;

    • 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;

    • t1.get().*f when N == 1 and f is a pointer to member data of a class T and t1 is an object of type reference_wrapper<U> where U is either the type T or a type derived from T.

    • (*t1).*f when N == 1 and f is a pointer to member data of a class T and t1 is not one of the types described in the previous item;

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

[2015-02, Cologne]

Waiting for implementation experience.

[2015-05, Lenexa]

STL: latest note from Cologne, waiting for implementation experience
STL: don't think this is harder than anything else we do
MC: it does involve mem_fn and invoke
STL: my simplication was not to attempt fine-grained
STL: can ignore pmf
STL: can't invoke pmf to reference wrapper
STL: wording dated back to TR1 when there was no decltype
MC: should decay_t<decltype(t1)> be pulled out since it is in multiple places
STL: it could be handled editorially
STL: we fix function, bind, invoke
STL: have not implemented this but believe it is fine
MC: Eric F, you have worked in invoke
EF: yes, looks ok
MC: consensus move to ready

Proposed resolution:

This wording is relative to N3936.

  1. Change 23.14.3 [func.require] p1 as depicted:

    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 Tis_base_of<T, decay_t<decltype(t1)>>::value is true;

    • (t1.get().*f)(t2, ..., tN) when f is a pointer to a member function of a class T and decay_t<decltype(t1)> is a specialization of reference_wrapper;

    • ((*t1).*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is not one of the types described in the previous itemdoes not satisfy the previous two items;

    • 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 Tis_base_of<T, decay_t<decltype(t1)>>::value is true;

    • t1.get().*f when N == 1 and f is a pointer to member data of a class T and decay_t<decltype(t1)> is a specialization of reference_wrapper;

    • (*t1).*f when N == 1 and f is a pointer to member data of a class T and t1 is not one of the types described in the previous itemdoes not satisfy the previous two items;

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