How to make template deduce return type of function with variadic arguments

Viewed 1002

Someone on stack overflow wrote an interesting way to capture a lambda or functor into your own class. I was trying to simplify it, and I think I got close but was having some trouble. Their example was:

// OT => Object Type
// RT => Return Type
// A ... => Arguments

template<typename OT, typename RT, typename ... A>
struct lambda_expression {
    OT _object;
    RT(OT::*_function)(A...)const; // A pointer to a member function, 
                                   // specifically the operator()

    lambda_expression(const OT & object) // Constructor
        : _object(object), 
          _function(&decltype(_object)::operator()) {} // Assigning the function pointer

    RT operator() (A ... args) const {
        return (_object.*_function)(args...);
    }
};

Basically this allows you to go:

int captureMe = 2;
auto lambda = [=](int a, int b) { return a + b + captureMe;};
lambda_expression<decltype(lambda), int, int, int>(lambda); 

I was trying to simplify this, and thought that the pointer contained in the lambda_expression class wouldn't be needed, because you can call the function object itself, instead of calling the pointer to the operator(). So I tried this:

template <typename OT, typename ... Args>   // No Return type specified
struct lambdaContainer
{
    lambdaContainer(OT funcObj) : funcObj(funcObj){ }
    OT funcObj; // No pointer, just the function object.

    auto operator()(Args... args) 
    {
        return funcObj(args...); // Call the function object directly
    }
};

Then something like:

int captureMe = 2;
auto lambda = [=](int a, int b) { return a + b + captureMe; };

lambdaContainer<decltype(lambda), int, int> lam(lambda);

auto i = lam(1, 1);
// i = 4;

Where I wrote the line:

auto operator()(Args... args) 
    {
        return funcObj(args...); 
    }

Apparently:

 decltype(auto) operator()(Args... args) //works in C++14 apparently.

But I tried without the auto keyword and I failed miserably in doing this, I want to understand how the Args... works. I tried:

decltype(funObj(Args...) operator()(Args... args) // this failed
decltype(OT(Args...) operator() (Args... args) // this failed
auto operator() (Args... args) -> decltype(funcObj(Args...)) // this failed
auto operator() (Args... args) -> decltype(OT(Args...)) // this failed

How can I expand the Args parameter so the template can deduce the return type? Is this only possible with auto?

1 Answers

decltype(e) takes an expression e and evaluates to the type of that expression. You need to provide an expression that represents the invocation of your stored lambda:

auto operator()(Args... args) 
    -> decltype(std::declval<OT>()(std::declval<Args>()...))

In this case, I'm using std::declval to create a "fake instance" of the objects that can be used for deduction purposes, without actually invoking any constructor.

Let's break this down even further:

-> decltype(
    std::declval<OT>()          // create a fake instance of `OT`
    (                           // invoke it
        std::declval<Args>()... // create a fake instance of each argument 
                                // type, expanding `Args...`
    )
)

live example on wandbox


By the way, you should still std::forward the arguments in your call to funcObj as there might be some rvalue references that need to be propagated further down:

auto operator()(Args... args) 
{
    return funcObj(std::forward<Args>(args)...); 
}
Related