Вариаден шаблонен метод и 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::функция вътре във функцията

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