drus
drus

Reputation: 505

Injecting lambda as parameter to template method

I'm trying to implement something similar to boost::static_visitor, have template function accepting lambda and supporting the following API:

int i1 = Apply([](int i) { return i; }); // doesn't compile
int i2 = Apply([]() { return 10; });     // ok
bool b1 = Apply([]() { return true; });  // ok
Apply([]() { return; });                 // ok

The question is continuation of this topic. The implementation

template <typename Function, typename Return = std::result_of_t<Function()>,
    typename = typename std::enable_if<!std::is_same<Return, void>::value>::type>
Return Apply(Function func)
{
    std::cout << "invoked via Return(*)(...)" << std::endl;
    return func();
}

template <typename Function, typename Return = std::result_of_t<Function()>, 
    typename = typename std::enable_if<std::is_same<Return, void>::value>::type>
void Apply(Function func)
{
    std::cout << "invoked via void(*)(...)" << std::endl;
    func();
}

works fine if lambda doesn't have parameters

#include <functional>
#include <type_traits>
#include <iostream>

template <typename Function, typename Return = std::result_of_t<Function()>, typename = typename std::enable_if<!std::is_same<Return, void>::value>::type>
Return Apply(Function func)
{
    std::cout << "invoked via Return(*)(...)" << std::endl;
    return func();
}

template <typename Function, typename Return = std::result_of_t<Function()>, typename = typename std::enable_if<std::is_same<Return, void>::value>::type>
void Apply(Function func)
{
    std::cout << "invoked via void(*)(...)" << std::endl;
    func();
}

int main()
{
    int i1 = Apply([]() { return 10; });
    bool b1 = Apply([]() { return true; });
    Apply([]() { return; });

    std::cout << i1 << " " << b1 << std::endl;
    return 0;
}

But if lambda has parameters, std::result_of_t< Function() > needs parameters list to be passed to deduce result type of lambda (e.g. std::result_of_t< Function(int) >).

Upvotes: 2

Views: 371

Answers (2)

Jive Dadson
Jive Dadson

Reputation: 17026

One size fits all. :-)

template <typename Function, typename... Args>
decltype(auto) Apply(Function&& func, Args&&... args) {
    std::cout << "invoked" << std::endl;
    return std::forward<Function>(func)(std::forward<Args>(args)...);
}

int main()
{
    int i1 = Apply([]() { return 10; });
    bool b1 = Apply([]( bool b)  { return !b; }, true);
    Apply([]() { return; });
    std::cout << i1 << " " << b1  << std::endl;
    return 0;
}

Or, better yet, just use std::invoke().

Upvotes: 5

super
super

Reputation: 12928

You can use a helper class to get the return type of the lambda.

template<typename T>
struct ReturnVal {};

template<typename ReturnType, typename Class, typename... Args>
struct ReturnVal<ReturnType(Class::*)(Args...)> {
    using type = ReturnType;
};

template<typename ReturnType, typename Class, typename... Args>
struct ReturnVal<ReturnType(Class::*)(Args...) const> {
    using type = ReturnType;
};

Then you can use it by passing the decltype of the lambdas operator().

template<typename Func>
void someTemplateFunction(Func func) {
    using Return = typename ReturnVal<decltype(&Func::operator())>::type;
}

With this approach you get the benefit of being able to pass in a function pointer to ReturnVal aswell. You can also use it to extract the parameter types.

Upvotes: 0

Related Questions