Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initialize class containing a std::function with a lambda

I created a template class containing a std::function as a member the following way:

template<typename Ret, typename... Args>
class Foo
{
private:
    std::function<Ret(Args...)> _func;

public:
    Foo(const std::function<Ret(Args...)>& func):
        _func(func)
    {}
};

In order not to have to specify the arguments and return type of the passed function, I created some make_foo overloads:

template<typename Ret, typename... Args>
auto make_foo(Ret (&func)(Args...))
    -> Foo<Ret, Args...>
{
    return { std::function<Ret(Args...)>(func) };
}

template<typename Ret, typename... Args>
auto make_foo(const std::function<Ret(Args...)>& func)
    -> Foo<Ret, Args...>
{
    return { func };
}

However, I was unable to create a make_foo overload that takes a lambda as parameter:

template<typename Ret, typename... Args>
auto make_foo(??? func)
    -> Foo<Ret, Args...>
{
    return { std::function<Ret(Args...)>(func) };
}

I just can't find a way to have the return type and argument types automatically deduced from the lambda. Is there an idiomatic way to solve such a problem?

like image 951
Morwenn Avatar asked Nov 12 '13 00:11

Morwenn


People also ask

How do you initialize a variable in a lambda function?

In C++14, you can introduce and initialize new variables in the capture clause, without the need to have those variables exist in the lambda function’s enclosing scope. The initialization can be expressed as any arbitrary expression; the type of the new variable is deduced from the type produced by the expression.

When does the INIT code run in lambda?

The INIT code runs when a new execution environment is run for the first time, and also whenever a function scales up and the Lambda service is creating new environments for the function. The initialization code is not run again if an invocation uses a warm execution environment.

Can a Lambda be assigned to a function?

It is true that a lambda can be assigned to a std::function, but that is not its native type. We’ll talk about what that means soon. As a matter of fact, there is no standard type for lambdas. A lambda’s type is implementation defined, and the only way to capture a lambda with no conversion is by using auto:

Why are lambda functions faster in C++ than C?

Because they are objects rather than pointers they can be inlined very easily by the compiler, much like functors. This means that calling a lambda many times (such as with std::sort or std::copy_if) is much better than using a global function. This is one example of where C++ is actually faster than C.


1 Answers

Ok, so I thought I would die, but I finally managed to do it ç_ç

First, I used the usual indices. Since I do not have the official ones, I used old indices I wrote some months ago:

template<std::size_t...>
struct indices {};

template<std::size_t N, std::size_t... Ind>
struct make_indices:
    make_indices<N-1, N-1, Ind...>
{};

template<std::size_t... Ind>
struct make_indices<0, Ind...>:
    indices<Ind...>
{};

Then, I used some function traits found somewhere on StackOverflow. They are nice, and I think that they are equivalent to the Boost library linked in the comments:

template<typename T>
struct function_traits:
    function_traits<decltype(&T::operator())>
{};

template<typename C, typename Ret, typename... Args>
struct function_traits<Ret(C::*)(Args...) const>
{
    enum { arity = sizeof...(Args) };

    using result_type = Ret;

    template<std::size_t N>
    using arg = typename std::tuple_element<N, std::tuple<Args...>>::type;
};

Then, I was able to write a proper make_foo function and it implementation function, since both are required to use indices. Be careful, it's plain ugly:

template<typename Function, std::size_t... Ind>
auto make_foo_(Function&& func, indices<Ind...>)
    -> Foo<
        typename function_traits<typename std::remove_reference<Function>::type>::result_type,
        typename function_traits<typename std::remove_reference<Function>::type>::template arg<Ind>...>
{
    using Ret = typename function_traits<typename std::remove_reference<Function>::type>::result_type;
    return { std::function<Ret(typename function_traits<typename std::remove_reference<Function>::type>::template arg<Ind>...)>(func) };
}

template<typename Function, typename Indices=make_indices<function_traits<typename std::remove_reference<Function>::type>::arity>>
auto make_foo(Function&& func)
    -> decltype(make_foo_(std::forward<Function>(func), Indices()))
{
    return make_foo_(std::forward<Function>(func), Indices());
}

The code is somehow ugly and unreadable, but it definitely works. Hope it does not rely on some implementation-defined behaviour now. Also, thanks all for your advice, it helped! :)

int main()
{
    auto lambda = [](int i, float b, long c)
    {
        return long(i*10+b+c);
    };

    auto foo = make_foo(lambda);
    std::cout << foo(5, 5.0, 2) << std::endl; // 57, it works!
}

And here is the live example :)

like image 109
Morwenn Avatar answered Oct 21 '22 20:10

Morwenn