Variadic template method and std::function - compilation error

568 views Asked by At

I'm sure the error is very simple and silly, but I can't see one. Here's the code:

#include <future>

template <typename ResultType>
class Foo
{
public:
    template <typename ...Args>
    void exec(const std::function<ResultType(Args...)>& task, Args&&... args) {}
};

int main()
{
   Foo<void>().exec([](){});
   return 0;
}

And here's the error:

'void CAsyncTask::exec(const std::function &,Args &&...)' : could not deduce template argument for 'const std::function &' with [ ResultType=void ]

Foo<void>().exec<void>([](){}) doesn't work either (and I'd much prefer not having to specify the Args types manually).

Update regarding the suggested answer: the following code does indeed work. CAsyncTask<void>().exec(std::function<void ()>([](){}));

But is there really no workaround for this problem? Can I expand my template somehow to deduce the lambda arguments?

3

There are 3 answers

0
Émilien Tlapale On BEST ANSWER

As Pradhan mentionned, the exact type of the std::function cannot be inferred from the lambda. You can cast it explicitly to solve the issue:

Foo<void>().exec(std::function<void()>([](){}));

Alternatively, you can simply use another typename for the function, without creating a std::function:

template <typename Callable, typename ...Args>
void exec(Callable&& task, Args&&... args);

Then Callable will accept several types of functors, including lambdas.

Note that there is a small performance penalty in using std::function objects in comparison to the template solution.

You could also add a static_assert to ensure that your Callable can be called with the given arguments, and display a meaningful message otherwise, rather than letting the compiler generate one at the actual call.

1
KABoissonneault On

You could try changing exec's signature to

template<typename Fn, typename... Args> 
void exec(Fn&& task, Args&&... args)

and then construct your std::function inside the function

3
David G On

A lambada is not a std::function, and as such types cannot be deduced from it. You'll want to make the std::function part a non-deducible context:

template<class T>struct identity_t{using type=T;};
template<class T>using identity=typename identity_t<T>::type;

template <typename ResultType>
class Foo
{
public:
    template <typename... Args>
    void exec(const std::function<ResultType(identity<Args>...)>& task,
              Args&&... args)
    {}
};

You can also make the function completely generic with template constraints;

#include <type_traits>

template<class...>struct voider{using type=void;};
template<class... Ts>using void_t=typename voider<Ts...>::type;

template<class T,class=void>
struct callable:std::false_type{};
template<class F,class... Ts>
using invoker=decltype(std::declval<F>()(std::declval<Ts>()...));
template<class F,class... Ts>
struct callable<F(Ts...),void_t<invoker<F,Ts...>>>:std::true_type{};

template<class R,class S>
using result_eq=std::is_same<std::result_of_t<S>,R>;

#define REQUIRE(cond) std::enable_if_t<(cond)>* = nullptr

template<class R>
class Foo
{
public:
    template<class F, class... Args, REQUIRE(callable<F(Args...)>{}), REQUIRE((result_eq<R, F(Args...)>{}))>
    void exec(F&& f, Args&&...);
};

int main()
{
    Foo<void>().exec([] (int) {}, 4);
}