Search code examples
functionc++11typesreferenceresult-of

C++11: Why result_of can accept functor type as lvalue_reference, but not function type as lvalue_reference?


I've got program below:

#include<type_traits>
#include<iostream>
using namespace std;
template <class F, class R = typename result_of<F()>::type>
R call(F& f) { return f(); }

struct S {
    double operator()(){return 0.0;}
};
int f(){return 1;}
int main()
{
    S obj;
    call(obj);//ok
    call(f);//error!
    return 0;
}

It fails to compile in the line of "call(f)". It's weird that "call(obj)" is OK.

(1) I've a similar post in another thread C++11 result_of deducing my function type failed. But it doesn't tell why functor objects are OK while functions are not.

(2) I'm not sure if this is related to "R call(F& f)": a function type cannot declare a l-value?

(3) As long as I know, any token with a name, like variable/function, should be considered a l-value. And in the case of function parameter, compiler should "decay" my function name "f" to a function pointer, right?

(4) This is like decaying an array and pass it to a function----And a function pointer could be an l-value, then what's wrong with "call(F& f)"?

Would you help to give some further explanations on "why" is my case, where did I get wrong? Thanks.


Solution

  • The problem with call(f) is that you deduce F as a function type, so it doesn't decay to a function pointer. Instead you get a reference to a function. Then the result_of<F()> expression is invalid, because F() is int()() i.e. a function that returns a function, which is not a valid type in C++ (functions can return pointers to functions, or references to functions, but not functions).

    It will work if you use result_of<F&()> which is more accurate anyway, because that's how you're calling the callable object. Inside call(F& f) you do f() and in that context f is an lvalue, so you should ask what the result of invoking an lvalue F with no arguments is, otherwise you could get the wrong answer. Consider:

    struct S {
      double operator()()& {return 0.0;}
      void operator()()&& { }
    };
    

    Now result_of<F()>::type is void, which is not the answer you want.

    If you use result_of<F&()> then you get the right answer, and it also works when F is a function type, so call(f) works too.

    (3) As long as I know, any token with a name, like variable/function, should be considered a l-value. And in the case of function parameter, compiler should "decay" my function name "f" to a function pointer, right?

    No, see above. Your call(F&) function takes its argument by reference, so there is no decay.

    (4) This is like decaying an array and pass it to a function----And a function pointer could be an l-value, then what's wrong with "call(F& f)"?

    Arrays don't decay when you pass them by reference either.

    If you want the argument to decay then you should write call(F f) not call(F& f). But even if you do that you still need to use result_of correctly to get the result of f() where f is an lvalue.