Метод шаблона Variadic и std::function - ошибка компиляции

Я уверен, что ошибка очень простая и глупая, но я не вижу ее. Вот код:

#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;
}

И вот ошибка:

'void CAsyncTask::exec(const std::function &,Args &&...)': не удалось вывести аргумент шаблона для 'const std::function &' с [ ResultType=void ]

Foo<void>().exec<void>([](){}) тоже не работает (и я бы предпочел не указывать типы Args вручную).

Обновление относительно предложенного ответа: следующий код действительно работает. CAsyncTask<void>().exec(std::function<void ()>([](){}));

Но действительно ли нет решения этой проблемы? Могу ли я как-то расширить свой шаблон, чтобы вывести лямбда-аргументы?


person Violet Giraffe    schedule 09.06.2015    source источник


Ответы (3)


Как упомянул Прадхан, точный тип std::function нельзя вывести из лямбды. Вы можете указать его явно, чтобы решить проблему:

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

Кроме того, вы можете просто использовать другое имя типа для функции, не создавая std::function:

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

Тогда Callable будет принимать несколько типов функторов, включая лямбда-выражения.

Обратите внимание, что при использовании объектов std::function по сравнению с решением шаблона наблюдается небольшое снижение производительности.

Вы также можете добавить static_assert, чтобы гарантировать, что ваш Callable может быть вызван с заданным аргументы и отображать значимое сообщение в противном случае, вместо того, чтобы позволять компилятору генерировать его при фактическом вызове.

person Émilien Tlapale    schedule 09.06.2015

Вы можете попробовать изменить подпись exec на

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

а затем создайте свою std::function внутри функции

person KABoissonneault    schedule 09.06.2015
comment
Да, только что пришла та же идея, и она работает! - person Violet Giraffe; 09.06.2015

Ламбада не является std::function, и поэтому из нее нельзя вывести такие типы. Вы захотите сделать часть std::function невыводимым контекстом:

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)
    {}
};

Вы также можете сделать функцию полностью универсальной с ограничениями шаблона;

#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);
}
person 0x499602D2    schedule 09.06.2015
comment
Хм. Я уже второй раз вижу трюк с идентификацией и до сих пор не понимаю... - person Violet Giraffe; 09.06.2015
comment
@VioletGiraffe Я думаю, это связано с тем фактом, что функции и структуры/классы не имеют одинаковых правил вывода шаблонов. Итак, мы используем структуру для вывода типа. - person KABoissonneault; 09.06.2015
comment
@VioletGiraffe Любой параметр, содержащий вложенный спецификатор имени, состоящий из параметра шаблона (например, void f(typename T::value_type)), является невыведенным контекстом. Параметр шаблона не будет выводиться из переданного аргумента. Псевдоним шаблона identity предназначен для более чистого синтаксиса, но что действительно работает, так это identity_t. Шаблон псевдонима заменяется на typename identity_t<T>::type, который является вложенным спецификатором имени, что делает его невыводимым контекстом. Тогда Args... будет выводиться только для второго пакета параметров, args.... - person 0x499602D2; 09.06.2015
comment
@ 0x499602D2: Ага! Думаю, теперь я понимаю. Спасибо! - person Violet Giraffe; 09.06.2015