Передача лямбда в качестве параметра функции шаблона

Почему не компилируется следующий код (в режиме С++ 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&& );

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