2017-01-29 22 views
18

Dlaczego poniższy kod się nie kompiluje (w trybie C++ 11)?Przekaż lambdę jako parametr funkcji szablonu

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

Komunikat o błędzie jest:

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

Ale to nie wyjaśnia, dlaczego nie może być zgodny z parametrem.

Jeśli zrobię qux funkcję non-szablon, zastępując From z T i To z int, kompiluje.

+2

Przede wszystkim 'qux' nie zwraca niczego, więc to nie powinno kompilować' auto bar = qux ... ' – nbro

+0

@nbro To nie ma nic wspólnego z problemem w zasięgu ręki. – emlai

+2

Jeśli wiesz tak bardzo, nie zadałbyś pytania. W każdym razie, przegłosowano, ponieważ nie wiem też dlaczego. – nbro

Odpowiedz

13

Funkcja lambda nie jest normalną funkcją. Każda lambda ma swój własny typ , który w żadnym wypadku nie jestTo (&)(const From&).
Non przechwytywanie lambda może rozpaść się To (*)(const From&) w przypadku korzystania:

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

Jak zauważył w komentarzach, to najlepsze co możesz zrobić, aby dostać się z lambda jest taka:

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

Uwaga: Założono, że wyprowadzenie typu zwrotu i typów argumentów jest obowiązkowe dla prawdziwego problemu. W przeciwnym razie możesz łatwo wydedukować całą lambdę jako ogólny obiekt wywoływalny i użyć go bezpośrednio, bez potrzeby niszczenia czegokolwiek.

+1

Dzięki! Przedrostek lambda z '* +' pozwala mi go przekazać przez ref. Muszę kochać C++ ... – emlai

+0

@tuple_cat dobry połów, dodam twój komentarz do odpowiedzi. – skypjack

+2

W rzeczywistości wystarczy tylko "*", bez potrzeby "* +". – emlai

10

Jeśli nie trzeba korzystać z dedukcji To typu, można po prostu wywnioskować typ całej parametr:

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

Tak, jest to zalecane podejście. – milleniumbug

+3

'std :: result_of' – milleniumbug

+1

To jest o wiele lepsze niż składanie ninja składni z' * + '... –

6

mnie poprawić, jeśli się mylę, ale parametry szablonu odliczenie dedukuje tylko dokładne typy bez rozważenia możliwych konwersji.

W wyniku kompilator nie może wywnioskować To i From dla To (&)(const From&) ponieważ qux oczekuje odwołanie do funkcji, ale trzeba zapewnić lambda, który ma swój własny rodzaj.

2

Nie pozostawiłeś absolutnie żadnych szans, aby kompilator zgadł, co to jest To. Dlatego musisz to wyraźnie określić.

Ponadto, lambda musi zostać przekazana za pomocą wskaźnika.

Wreszcie, ta wersja kompiluje 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

czekasz zarówno niejawne konwersje typów (od bezimiennego typu obiektu funkcja funkcjonować typ referencyjny) oraz rodzaju szablon odliczenia wydarzy. Jednak, you can't have both, musisz znać typ celu, aby znaleźć odpowiednią sekwencję konwersji.

1

Ale to nie wyjaśnia, dlaczego nie może dopasować parametru.

Odliczenie szablonów dokładnie dopasowuje typy. Jeśli nie można wywnioskować typów, odliczenie nie powiedzie się. Konwersje nigdy nie są brane pod uwagę.

W wyrażeniu:

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

typu ekspresji lambda pewne unikalny, Nienazwana typu. Jakikolwiek jest ten typ, to na pewno nie jest to To(const From&) - więc dedukcja się nie udaje.


Jeśli zrobię qux funkcję non-szablon, zastępując From z T i To z int, kompiluje.

To nie jest prawda. Jednakże, jeśli argumentem byłby wskaźnik do funkcji, a nie jako odniesienie do funkcji, to byłby. Dzieje się tak, ponieważ lambda bez przechwytywania jest niejawnie przekształcalna na równoważny typ wskaźnika funkcji. Ta konwersja jest dozwolona poza kontekstem dedukcji.

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 

Jest to ten sam powód, dlaczego to się nie powiedzie:

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

Ale to udaje:

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

Korzystnym rozwiązaniem byłoby wywnioskować typ wymagalne i wybierz wynik, używając: std::result_of:

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

Teraz możesz w prosty sposób przekazać obiekt lambda, funkcję lub funkcję.

Powiązane problemy