Передача лямбда в качестве параметра функции шаблона
Почему не компилируется следующий код (в режиме С++ 11)?
#include <vector>
template<typename From, typename To>
void qux(const std::vector<From>&, To (&)(const From&)) { }
struct T { };
void foo(const std::vector<T>& ts) {
qux(ts, [](const T&) { return 42; });
}
Сообщение об ошибке:
prog.cc:9:5: error: no matching function for call to 'qux'
qux(ts, [](const T&) { return 42; });
^~~
prog.cc:4:6: note: candidate template ignored: could not match 'To (const From &)' against '(lambda at prog.cc:9:13)'
void qux(const std::vector<From>&, To (&)(const From&)) { }
^
Но это не объясняет, почему он не может соответствовать параметру.
Если я создаю qux
функцию без шаблона, заменив From
на T
и To
на int
, она компилируется.
Ответы
Ответ 1
Лямбда-функция не является нормальной функцией. Каждая лямбда имеет свой собственный тип , который не является To (&)(const From&)
в любом случае.
Не захватывающая лямбда может распадаться до To (*)(const From&)
в вашем случае, используя:
qux(ts, +[](const T&) { return 42; });
Как отмечено в комментариях, лучшее, что вы можете сделать, чтобы получить его из лямбда, следующее:
#include <vector>
template<typename From, typename To>
void qux(const std::vector<From>&, To (&)(const From&)) { }
struct T { };
void foo(const std::vector<T>& ts) {
qux(ts, *+[](const T&) { return 42; });
}
int main() {}
Примечание. Я предположил, что вывод возвращаемого типа и типов аргументов является обязательным для реальной проблемы. В противном случае вы можете легко вывести всю лямбду как общий вызываемый объект и использовать его напрямую, не нужно ничего распадать.
Ответ 2
Если вам не нужно использовать выводимый тип To
, вы можете просто вывести тип всего параметра:
template<typename From, typename F>
void qux(const std::vector<From>&, const F&) { }
Ответ 3
Исправьте меня, если я ошибаюсь, но вывод параметров шаблона выводит только точные типы без учета возможных преобразований.
В результате компилятор не может выводить To
и From
для To (&)(const From&)
, потому что qux
ожидает ссылку на функцию, но вы предоставляете лямбду, которая имеет свой собственный тип.
Ответ 4
У вас не было абсолютно никакого шанса компилятору угадать, что такое To
. Таким образом, вам нужно явно указать его.
Кроме того, lambda здесь нужно передать указателем.
Наконец, эта версия компилируется нормально:
template<typename From, typename To>
void qux(const std::vector<From>&, To (*)(const From&)) { }
struct T { };
void foo(const std::vector<T>& ts) {
qux<T,int>(ts,[](const T&) { return 42; });
}
Ответ 5
Ожидаются как неявные преобразования типов (от типа неименованного объекта функции до типа ссылочного типа), так и вывод типа шаблона. Однако у вас не может быть обоих, так как вам нужно знать тип цели, чтобы найти подходящую последовательность преобразования.
Ответ 6
Но это не объясняет, почему он не может соответствовать параметру.
Вычисление шаблонов пытается точно соответствовать типам. Если типы не могут быть выведены, дедукция терпит неудачу. Конверсии никогда не рассматриваются.
В этом выражении:
qux(ts, [](const T&) { return 42; });
Тип выражения лямбда - это уникальный, неназванный тип. Каким бы ни был этот тип, это определенно не To(const From&)
- поэтому дедукция терпит неудачу.
Если я создаю qux
функцию без шаблона, заменив From
на T
и To
на int
, она компилируется.
Это неверно. Однако, если аргумент был указателем на функцию, а не ссылкой на функцию, то это было бы. Это связано с тем, что лямбда без захвата неявно конвертируется в эквивалентный тип указателя функции. Это преобразование разрешено вне контекста вывода.
template <class From, class To>
void func_tmpl(From(*)(To) ) { }
void func_normal(int(*)(int ) ) { }
func_tmpl([](int i){return i; }); // error
func_tmpl(+[](int i){return i; }); // ok, we force the conversion ourselves,
// the type of this expression can be deduced
func_normal([](int i){return i; }); // ok, implicit conversion
Это по той же причине, почему это не удается:
template <class T> void foo(std::function<T()> );
foo([]{ return 42; }); // error, this lambda is NOT a function<T()>
Но это преуспевает:
void bar(std::function<int()> );
bar([]{ return 42; }); // ok, this lambda is convertible to function<int()>
Предпочтительным подходом было бы вывести тип вызываемого и выбрать результат, используя std::result_of
:
template <class From,
class F&&,
class To = std::result_of_t<F&&(From const&)>>
void qux(std::vector<From> const&, F&& );
Теперь вы можете легко передать свою лямбду, функцию или объект функции.