web-dev-qa-db-fra.com

Passer lambda comme paramètre de fonction template

Pourquoi le code suivant ne compile-t-il pas (en mode C++ 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; });
}

Le message d'erreur est:

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&)) { }
     ^

Mais cela n'explique pas pourquoi il ne pourrait pas correspondre au paramètre.

Si je fais de qux une fonction autre que de modèle, en remplaçant From par T et To par int, il est compilé.

17
emlai

Une fonction lambda n'est pas une fonction normale. Chaque lambda a son propre type ce n'est pas To (&)(const From&) dans tous les cas.
Un lambda sans capture peut se désintégrer en To (*)(const From&) dans votre cas en utilisant:

qux(ts, +[](const T&) { return 42; });

Comme indiqué dans les commentaires, le mieux que vous puissiez faire pour obtenir un lambda est le suivant:

#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() {}

Remarque: j'ai supposé que la déduction du type de retour et des types d'arguments est obligatoire pour le problème réel. Sinon, vous pouvez facilement déduire le lambda entier en tant qu'objet appelable générique et l'utiliser directement, sans avoir besoin de décomposer.

13
skypjack

Si vous n'avez pas besoin d'utiliser le type To déduit, vous pouvez simplement en déduire le type du paramètre entier:

template<typename From, typename F>
void qux(const std::vector<From>&, const F&) { }
10
emlai

Corrigez-moi si je me trompe, mais la déduction des paramètres de modèle ne déduit que les types exacts sans tenir compte des conversions possibles.

En conséquence, le compilateur ne peut pas déduire To et From pour To (&)(const From&) car qux attend une référence à function, mais vous fournissez un lambda qui a son propre type.

6
Edgar Rokjān

Vous n'avez laissé aucune chance au compilateur de deviner ce qui est To. Ainsi, vous devez le spécifier explicitement.

Aussi, lambda ici doit être passé par un pointeur.

Enfin, cette version compile ok:

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; });
}
2
AndreyS Scherbakov

Vous attendez des conversions de type implicites (d'un type d'objet de fonction non nommé à un type de référence de fonction) et une déduction de type de modèle. Cependant, vous ne pouvez pas avoir les deux , car vous devez connaître le type de cible pour trouver la séquence de conversion appropriée.

2
milleniumbug

Mais cela n'explique pas pourquoi il ne pourrait pas correspondre au paramètre.

La déduction de modèle tente de faire correspondre les types exactement. Si les types ne peuvent pas être déduits, la déduction échoue. Les conversions ne sont jamais considérées. 

Dans cette expression:

qux(ts, [](const T&) { return 42; });

Le type de l'expression lambda est quelque-type unique , unnamed type. Quel que soit ce type, ce n'est certainement pas To(const From&) - la déduction échoue. 


Si je fais de qux une fonction autre que de modèle, en remplaçant From par T et To par int, il est compilé.

Ce n'est pas vrai. Cependant, si l'argument était un pointeur à fonctionner plutôt qu'un référence à fonctionner, alors ce serait le cas. En effet, un lambda sans capture est implicitement convertible en type de pointeur de fonction équivalent. Cette conversion est autorisée en dehors du contexte de déduction.

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

C'est la même raison pour laquelle cela échoue:

template <class T> void foo(std::function<T()> );
foo([]{ return 42; }); // error, this lambda is NOT a function<T()>

Mais cela réussit:

void bar(std::function<int()> );
bar([]{ return 42; }); // ok, this lambda is convertible to function<int()>

L’approche privilégiée serait de déduire le type de l’appelable et de choisir le résultat à l’aide de std::result_of :

template <class From,
    class F&&,
    class To = std::result_of_t<F&&(From const&)>>
void qux(std::vector<From> const&, F&& );

Maintenant, vous pouvez passer votre objet lambda, ou fonction, ou fonction parfaitement. 

1
Barry