Search code examples
c++c++14pointer-to-memberresult-of

result_of does not define type for mem_fn


I have the following piece of code:

#include <functional>

struct X {
    int get() const& {
        return 42;
    }
};

template<typename Func>
std::result_of_t<Func(X)> Apply(Func fn) {
    X x;
    return fn(x);
}

int main(void) {
    Apply([](X const& x){return x.get();});
    //Apply(std::mem_fn(&X::get)); // does not compile
}

The first call to Apply compiles fine, but if I uncomment the second call, I get the following compilation error:

main.cpp:16:5: error: no matching function for call to 'Apply'
    Apply(std::mem_fn(&X::get)); // does not compile
    ^~~~~
main.cpp:10:27: note: candidate template ignored: substitution failure [with Func = std::_Mem_fn<int (X::*)() const &>]: no type named 'type' in 'std::result_of<std::_Mem_fn<int (X::*)() const &> (X)>'
std::result_of_t<Func(X)> Apply(Func fn) {
                          ^

I somehow expected that both calls could be used interchangeably and that std::mem_fn just "would do the right thing". Can anybody explain, what happens here?


Solution

  • The problem is here:

    int get() const& {
    //            ^^^
    

    Your member function is lvalue-reference qualified. In your Apply():

    template<typename Func>
    std::result_of_t<Func(X)> Apply(Func fn) {
        return fn(X{});
    }
    

    you're invoking it with an rvalue. Which brings us to the [very surprising to me] difference between these two expressions:

    X{}.get();        // ok
    (X{}.*&X::get)(); // ill-formed
    

    On specifically pointer-to-member operators, the ref-qualifiers of the member pointer are checked against the value category of the object. From [expr.mptr.oper]:

    In a .* expression whose object expression is an rvalue, the program is ill-formed if the second operand is a pointer to member function with ref-qualifier &. In a .* expression whose object expression is an lvalue, the program is ill-formed if the second operand is a pointer to member function with ref-qualifier &&.

    So the first expression is okay, get() is const&-qualified but rvalues can bind to that. The second expression is not okay - the rules just explicitly prohibit it.

    So the behavior you see is perfectly correct - mem_fn is defined by directly invoking the member function, which is ill-formed on an rvalue, so Apply is removed from the overload set. If it were not, then instantiating the body would be a hard error.

    The reason the lambda works is that the temporary X is bound to the lambda's reference parameter. get() is then invoked on the lvalue function parameter - not on the temporary passed into it. But even without that, invoking get() directly on the temporary would still be fine.