2017-08-06 46 views
15

Gram z std::variant, lambdas i std::future i otrzymałem super dziwne wyniki, gdy próbowałem je skomponować. Oto przykłady:Nie można zainicjować std :: variant z różnymi wyrażeniami lambda

using variant_t = std::variant< 
    std::function<std::future<void>(int)>, 
    std::function<void(int)> 
>; 
auto f1 = [](int) { return std::async([] { return 1; }); }; 
auto f2 = [](int) { return std::async([] { }); }; 

variant_t v1(std::move(f1)); // !!! why DOES this one compile when it SHOULDN'T? 
auto idx1 = v1.index(); //equals 1. WHY? 

variant_t v2(std::move(f2)); // !!! why DOESN'T this one compile when it SHOULD? 

tu jest błąd kompilacji:

Error C2665 'std::variant<std::function<std::future<void> (int)>,std::function<void (int)>>::variant': none of the 2 overloads could convert all the argument types

OK, pozwala zmieniać variant „s przedmioty podpisów powrocie void do int:

using variant_t = std::variant< 
    std::function<std::future<int>(int)>, 
    std::function<int(int)> 
>; 

variant_t v1(std::move(f1)); // COMPILES (like it should) 
auto idx1 = v1.index(); // equals 0 

variant_t v2(std::move(f2)); // DOESN'T compile (like it should) 

Co jest do cholery dzieje się tutaj? Dlaczego std::future<void> jest tak wyjątkowy?

+1

Uwaga: 'std :: variant' jest funkcją C++ 17, a nie C++ 11. – Rakete1111

+1

Twój błąd kompilacji pochodzi z drugiego przykładu, ale twoje pytanie sprawia, że ​​wydaje się, że pochodzi z twojego pierwszego. – Barry

Odpowiedz

16

Konwerterowy szablon konstruktora wykorzystuje rozdzielczość przeciążania, aby określić, jaki typ powinien mieć zbudowany obiekt. W szczególności oznacza to, że jeśli konwersje do tych typów są równie dobre, konstruktor nie działa; w twoim przypadku działa dokładnie jedna ze specjalizacji std::function jest możliwa do skonstruowania z twojej argumentacji.

Więc kiedy jest function<...> możliwe do skonstruowania z podanego argumentu? Począwszy od C++ 14, jeśli argument można wywołać z typami parametrów i otrzymamy typ o wartości convertible to the return type. Zauważ, że zgodnie z tą specyfikacją, jeśli typem zwrotnym jest void, wszystko idzie (jako any expression can be converted to void with static_cast). Jeśli masz function powracający void, funktor, który przekażesz, może zwrócić wszystko, co jest —, to funkcja, a nie błąd! Właśnie dlatego function<void(int)> ma zastosowanie do f1. Z drugiej strony, future<int> nie przekształca się w future<void>; Stąd tylko function<void(int)> jest opłacalne i wskaźnik wariantu jest 1.

Jednak w tym drugim przypadku, lambda powraca future<void>, którą można przekształcić zarówno future<void> i void. Jak wspomniano powyżej, powoduje to, że obie specjalizacje są opłacalne, dlatego też variant nie może zdecydować, który z nich skonstruować.

Na koniec, jeśli dostosujesz typ zwrotu do int, unikniesz całej tej kwestii konwersji void, więc wszystko działa zgodnie z oczekiwaniami.

+0

dzięki za odpowiedź. Ale nadal nie mogę sobie wyobrazić, w jaki sposób 'std :: future ' może być niejawnie wymienialne na 'void' (lub' void' na 'std :: future ' - nie jestem pewien czy zrozumiałem ten punkt)? Czy to oznacza, co everithing można zamienić na "void"? –

+3

@DmitryKatkevich To prawda, każde wyrażenie można przekonwertować na 'void' przez' static_cast'. W przeciwnym razie nie moglibyśmy dostarczyć funktorów do 'funkcji', które zwracają coś, gdy' funkcja' daje 'void', co jest pożądaną cechą! – Columbo