9

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?

phimuemue
  • 34,669
  • 9
  • 84
  • 115

1 Answers1

7

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 2
    `const &` binds to rvalues, though. – T.C. Feb 08 '17 at 13:06
  • @T.C. yes, but ref-qualifiers are not actually about binding a reference -- they restrict the calling object's category directly. – Quentin Feb 08 '17 at 13:08
  • 2
    @Quentin Not quite. `X().get()` compiles. – T.C. Feb 08 '17 at 13:09
  • 2
    The issue here is that [expr.mptr.oper]/6 has the *ref-qualifier* on a pointer to member function unconditionally restrict the value category of the left operand, unlike ordinary overload resolution in the case of `X().get()`, which uses standard reference binding rules. – T.C. Feb 08 '17 at 13:10
  • @T.C. oh wow, it does. I'm stumped -- definitely out of my league on this one :) – Quentin Feb 08 '17 at 13:10
  • 2
    @T.C. So `X{}.get()` is okay, but `(X{}.*&X::get)()` is not okay? Why? (I mean, I see the rule, but why the differentiation?) – Barry Feb 08 '17 at 13:11
  • Now that I think about it, I assume it has something to do with overload resolution. Can't really overload `(x.*&X::p)()`, but could overload `x.p()` – Barry Feb 08 '17 at 13:18
  • It's not too hard to hack either rule to make them consistent one way or the other. I'd imagine that this was simple oversight. – T.C. Feb 08 '17 at 13:21
  • @Barry I'm not sure if I can follow you. I agree that I invoke `fn` on an r-value. But as far as I can see, the error message just complains that `result_of` does not expose `type`. So apparently it is not d'accord with the signature of `Apply`, which should be independent of the body of `Apply`. I edited my question to reflect this and still get the same error. Or am I missing something? – phimuemue Feb 08 '17 at 14:00
  • 4
    @phimeumue There's no `type` because that invoke expression is ill-formed, and it's ill-formed because of the rules around member pointers. If you change your `result_of_t` to just `auto`, you'd see the more direct error. – Barry Feb 08 '17 at 14:09