6

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?

Morwenn
  • 21,684
  • 12
  • 93
  • 152
  • You should just be able to replace everything with `T` (or `F` if you prefer). – chris Nov 12 '13 at 00:38
  • 1
    Maybe check out [this Boost library](http://www.boost.org/libs/function_types/doc/html/boost_functiontypes/reference/decomposition.html). – Kerrek SB Nov 12 '13 at 00:44
  • The overload the takes `std::function` as an argument is enough to permit lambdas. In fact, you should use it as the only overload because it accepts both lambdas and function pointers. – David G Nov 12 '13 at 00:46
  • @0x499602D2 Well, it [does not seem to work](http://coliru.stacked-crooked.com/a/986ab079122623ed) though :/ – Morwenn Nov 12 '13 at 00:49
  • @0x499602D2: Not for lambdas. There are too many user-defined conversions. – Kerrek SB Nov 12 '13 at 00:52
  • @chris I tried, but I store a tuple of type `std::tuple` in the body of the class. So I need to have `Args...` as a class template parameter. – Morwenn Nov 12 '13 at 09:48
  • if you also store a tuple with arguments, why don't you just obtain their types from said arguments? – R. Martinho Fernandes Nov 12 '13 at 20:03
  • @R.MartinhoFernandes Probably because the `std::tuple` is private and `make_foo` cannot access it. – Morwenn Nov 12 '13 at 20:10
  • that does not explain anything. what does the tuple store? where do these arguments come from? take it from there. (please don't say two-phase initialisation) – R. Martinho Fernandes Nov 12 '13 at 20:24
  • @R.MartinhoFernandes If you want the whome story, the complete `Foo` class was actually a functor memoizing a given pure function. Therefore, I use a `std::unordered_map, Ret>` to cache the couples args/result. – Morwenn Nov 12 '13 at 20:29

2 Answers2

4

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 :)

Community
  • 1
  • 1
Morwenn
  • 21,684
  • 12
  • 93
  • 152
  • Had I discovered [this proposal](https://docs.google.com/viewer?a=v&pid=forums&srcid=MTEwODAzNzI2MjM1OTc0MjE3MjkBMDEyMjk4ODM3NDg5MzA1MDk3ODEBenp6WHdEOURSTThKATQBaXNvY3BwLm9yZwF2Mg) earlier, I would have been less painful... – Morwenn Nov 13 '13 at 19:12
  • Morwenn to the rescue 2x in a week...I happened to need this. It seems to work, but I've seen some FUD over ["generic lambdas"](https://en.wikipedia.org/wiki/C%2B%2B14#Generic_lambdas) on similar answers that didn't actually fit my use case the way this one does. As the implications of what's written here are hard for me to follow, I'll only mention it and ask *"do you think generic lambdas will break this?"* (Note: also, that proposal link is dead now...) – HostileFork says dont trust SE Dec 19 '14 at 03:32
  • 1
    @HostileFork It will probably break. While it properly handles functions and lambdas with a fixed types, it breaks as soon as there are overloads or templates since it `&T::operator()` cannot resolve the address of an overloaded function. The bottom line is that if you can abstract away the type of the function then please, do so, and then if nothing else works, you can try to use `function_traits` (note that they also exist in Boost). The dead link was a link to a problem that also provided some functions traits. – Morwenn Dec 19 '14 at 10:55
2

I have an example that works with mutable lambdas. I can't quite figure out how to get the CV member qualification right.

First, here's the function template we're after:

#include <functional>

template <typename R, typename ...Args>
void foo(std::function<R(Args...)> f)
{ }

Now we'll let a function template bar take an arbitrary lambda and call the right version of foo, by inspecting the type of the lambda's operator():

#include <type_traits>

template <typename> struct remove_member;

template <typename C, typename T>
struct remove_member<T C::*>
{ using type = T; };

template <typename F>
void bar(F f)
{
    using ft = decltype(&F::operator());
    foo(std::function<typename remove_member<ft>::type>(f));
}

Example:

int q;
bar([&](int a, int b) mutable -> int { q = a + b; return q / b; });

You can use normal, const lambdas with this modified trait, though I don't like having to spell the function type out:

template <typename C, typename R, typename ...Args>
struct remove_member<R (C::*)(Args...) const>
{ using type = R(Args...); };

I thought it might work with the original code if I use typename std::remove_cv<T>::type, but at least on GCC this doesn't work because of some strange __attribute__((const)) that's set on the lambda's operator type which seems to interfere with the template specialization.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Upvoting your challenge to the downvoter and answer, although I used [@Morwenn's solution](http://stackoverflow.com/a/19934080/211160) (a bit convoluted though it may be). I'd be curious on your thoughts on it and if ["generic lambdas"](https://en.wikipedia.org/wiki/C%2B%2B14#Generic_lambdas) throw in any curveballs, because in my brief survey they have been mentioned as defeating some hacks in solving this problem *(although it's hard to find a question that exactly maps without someone saying "that's not what you really want")*. – HostileFork says dont trust SE Dec 19 '14 at 04:14
  • 1
    @HostileFork: I suppose you couldn't use `decltype(&F::operator())` on a generic lambda, since that member function is a template. – Kerrek SB Dec 19 '14 at 08:03