2016-05-17 17 views
5

Poniższy kod działa poprawnie na VS2015:Template Selection Function podstawie typu zagnieżdżonego

struct Foo 
{ 
    using Bar = int; 
    auto operator()() { return "Foo!"; } 
}; 

template <typename Callable, typename CodeType> // <<< CodeType is a template param 
void funky(CodeType code, Callable func) 
{ 
    cout << "Generic: " << code << ", " << func() << endl; 
} 

template <typename HasBar> 
void funky(typename HasBar::Bar code, HasBar func) // <<< The code type is a nested type 
{ 
    cout << "Has Bar: " << code << ", " << func() << endl; 
} 

int main() 
{ 
    Foo foo; 
    funky(3, []() { return "Lambda!"; }); 
    funky(3, foo); 
    return 0; 
} 

Druk:

Generic: 3, Lambda! 
Has Bar: 3, Foo! 

Jednak it does not compile na gcc/brzękiem, narzekając:

In function 'int main()': 
27:16: error: call of overloaded 'funky(int, Foo&)' is ambiguous 
27:16: note: candidates are: 
12:6: note: void funky(CodeType, Callable) [with Callable = Foo; CodeType = int] 
18:6: note: void funky(typename HasBar::Bar, HasBar) [with HasBar = Foo; typename HasBar::Bar = int] 

Niejednoznaczność jest rozwiązana poprawnie przez VS2015 (co nie oznacza, że ​​jest to zgodne z d o).

Jak mogę to skompilować i poprawnie uruchomić na Clang/gcc?
Pomyślałem o użyciu std::enable_if, ale nie mogłem zrobić tego, co chcę (najprawdopodobniej użyłem go niepoprawnie). Jeśli jest to droga, w jaki sposób należy ją zastosować, aby rozwiązać tę niejednoznaczność?

UPDATE:
Dodawanie typename HasBar :: Bar do params szablonu dostaje gcc/Clang zbudować i uruchomić kod poprawnie:

template <typename HasBar, typename HasBar::Bar> 
void funky(typename HasBar::Bar code, HasBar func) // <<< The code type is a nested type 
{ 
    cout << "Has Bar: " << code << ", " << func() << endl; 
} 

Wydaje się poinformować kompilator, że istnieje druga , wartość parametru szablonu non-type (nieużywana w kodzie funkcji), która jest typu typename HasBar::Bar. If typename HasBar::Bar nie istnieje, SFINAE usunie tę funkcję ze zbioru przeciążeniowego i zostanie wybrana forma ogólna.

Jednak, kiedy już istnieje, nie wiem, dlaczego ta funkcja będzie miała pierwszeństwo przed pierwszą. Chyba dlatego, że jest bardziej wyspecjalizowany - choć specjalizacja nie jest używana w samym kodzie. Jednak w tym przypadku było już ono bardziej wyspecjalizowane nawet przed nowym paramem!

Jednak, w tym przypadku VS2015 zawsze wybiera formę ogólną daje złą odpowiedź!

Czy istnieje pewna składnia (i/lub sposób obejścia), który będzie działał we wszystkich przypadkach?

+0

W jaki sposób użyłeś 'enable_if'? Czy użyłeś go, aby włączyć drugie przeciążenie, lub * wyłączyć * pierwsze przeciążenie? –

+0

Właściwie próbowałem różnych rzeczy, ale nie jestem pewien, czy były poprawne. Jak * powinno * być użyte tutaj? –

+1

Spodziewałbym się, że druga funkcja będzie bardziej wyspecjalizowana, a tym samym będzie preferowanym przeciążeniem, jeśli ma to zastosowanie (więc VS miałoby rację). Czy ktoś może to komentować? – MikeMB

Odpowiedz

2

zrobiłbym to z SFINAE (https://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error) oszukać w taki sposób, jak to:

#include <iostream> 
#include <type_traits> 

using namespace std; 

struct Foo 
{ 
    using Bar = int; 
    auto operator()() { return "Foo!"; } 
}; 

template< typename ... Ts > 
using void_t = void; 

template< typename T, typename = void > 
struct has_type_Bar : false_type{}; 

template< typename T > 
struct has_type_Bar< T, void_t<typename T::Bar> > : true_type {}; 

template <typename Callable, typename CodeType> 
void funky_impl(CodeType code, Callable func, false_type) 
{ 
    cout << "Generic: " << code << ", " << func() << endl; 
} 

template <typename Callable, typename CodeType> 
void funky_impl(CodeType code, Callable func, true_type) 
{ 
    cout << "Has Bar: " << code << ", " << func() << endl; 
} 

template <typename Callable, typename CodeType> 
void funky(CodeType code, Callable func) 
{ 
    return funky_impl(code, func, has_type_Bar<CodeType>{}); 
} 

int main() 
{ 
    Foo foo; 
    funky(3, []() { return "Lambda!"; }); 
    funky(3, foo); 
    return 0; 
} 

Dla VS2015, myślę, że ma błąd z nazwą dwufazowym odnośnika (What exactly is "broken" with Microsoft Visual C++'s two-phase template instantiation?).

+0

To działa, ale czy istnieje mniej gadatliwy, bardziej ogólny sposób, aby to zrobić? Być może z bardziej 'std' utils? –

+0

Skończyłem do korzystania z tego, ponieważ jest to jedyna rzecz, która działała konsekwentnie, w tym na VS2015. –

4

13 minut później ... odpowiadając na siebie. [NOT] Rozwiązany!

Dodawanie typename HasBar::Bar do szablonu params did the trick:

template <typename HasBar, typename HasBar::Bar> 
void funky(typename HasBar::Bar code, HasBar func) // <<< The code type is a nested type 
{ 
    cout << "Has Bar: " << code << ", " << func() << endl; 
} 

Wydaje się poinformować kompilator, że istnieje drugi, non-type wartość parametru szablonu (nieużywany w kodzie funkcji), które jest typu typename HasBar::Bar. Jeśli nie istnieje typename HasBar::Bar, SFINAE usunie tę funkcję ze zbioru przeciążeniowego i zostanie wybrana forma ogólna.

Jednak, gdy istnieje , istnieje, nie wiem, dlaczego ta funkcja będzie miała pierwszeństwo przed pierwszą. Chyba dlatego, że jest bardziej wyspecjalizowany - choć specjalizacja nie jest używana w samym kodzie.
Jednak w tym przypadku było już ono bardziej wyspecjalizowane nawet przed nowym paramem!

+0

Czy możesz (lub ktoś inny) również wyjaśnić, dlaczego tak działa? Standardowe cytaty będą mile widziane. –

+0

@ TamásSzelei: Używa SFINAE do usunięcia zaksięgowanej funkcji z zestawu przeciążeniowego, jeśli HasBar :: Bar nie jest poprawny (jak w "jeśli' HasBar' nie ma zagnieżdżonego typu 'Bar") – MikeMB

+0

Wyłączy to Naprawdę, naprawia gcc/clang, ale łamie VS2015 - powodując, że daje złą odpowiedź :-( –

3

Odpowiadając na pytanie, dlaczego ...

Łatwym sposobem, aby zrozumieć to zrobić ręcznie, co kompilator zrobi.

Dane:

// first form 
template <typename Callable, typename CodeType> 
void funky(CodeType code, Callable func); 

// second form 
template <typename HasBar, typename HasBar::Bar> 
void funky(typename HasBar::Bar code, HasBar func); 

stosując lambda, Int:

pierwsza postać: template<lambda, int> void funky(int, lambda) - cienka wymagane dwa podstawienia

Druga postać: template<lambda, lambda::Bar> < - błąd tutaj, ponieważ N SFNAE nie ma typu podrzędnego Bar.

Dlatego pierwsza forma jest wybrana (jako jedyny prawny)

zastępując HasBar, HasBar :: słupkowego

pierwsza postać: template<HasBar, HasBar::Bar> void funky(HasBar::Bar, HasBar) - cienka wymagane dwa podstawienia

Druga postać: template<HasBar, HasBar::Bar> < - Dokładne, wymagane podstawienia zerowe.

Oba są prawidłowe, ale druga forma wymaga mniejszej liczby zastępstw szablonów. Dlatego jest bardziej wyspecjalizowany i lepiej dopasowany.

w odpowiedzi na pytania:

ok ... pozwala myśleć o jeden sposób możemy realizować algorytm template match w kompilator ...

W punkcie niniejszego zaproszenia funky(3, []() { return "Lambda!"; });:

compute the "ambiguity" of each template... 
let's measure ambiguity as the total number of combinations of 
known types that would make a match. 
This would be a set of possible substitutions. The set is limited 
to the product of the total number of types known for each template 
argument... for now let's just consider the ones mentioned in the program and ignore all others (like float, ostream etc.) 

for the first form: template<Callable, CodeType> 
Callable can be: Foo, int, some_lambda 
When Callable is Foo, CodeType can be: Foo, int, some_lambda 
When Callable is int, CodeType can be: Foo, int, some_lambda 
... etc 
for a total ambiguity score of 3 * 3 = 9 

now the second form: template<HasBar, HasBar::Bar> 
HasBar can be: Foo, int, some_lambda 
When HasBar is Foo, HasBar::Bar can only be: Foo::Bar, because Foo::int and Foo::some_lambda are not legal syntax = 1 
When HasBar is int, HasBar::Bar can't be anything because int::something is illegal, so score = 0 
When HasBar is some_lambda, HasBar::Bar can't be anything because lambda::something is illegal, so score = 0 
... for a total ambiguity score of 1 + 0 + 0 = 1 

... ok, now we know how ambiguous each template is, let's see if the arguments can be substituted into one of the options for each... 
... if the arguments can't be legally substituted, dump the template as an option ... 
... if we have any templates left in our 'legal' set, choose the one with the least 'ambiguity' 
... if there is more than one legal templates with the minimum ambiguity, error (as you saw in the beginning) 
... otherwise, we have a match - use this template function. 
+0

Dwie rzeczy: (1) Czy to oznacza, że ​​wybór nie bierze pod uwagę rzeczywistych parametrów dla zestawu przeciążenia, ponieważ to już zawierało ' typename HasBar :: Bar'? (2) Czy możesz bardziej szczegółowo opisać, co masz na myśli przez zastąpienie?Dlaczego druga forma w drugim przypadku 0 zastępstw? Czy możesz wyraźnie określić substytucje w odpowiedzi? –

+0

@AdiShavit typy testowane względem argumentów szablonu są wyprowadzane z typów argumentów na stronie wywołania. W drugiej formie HasBar jest argumentem szablonu (tzn. Musi być dopasowany), ale gdy już to zrobi, 'HasBar :: Bar' nie jest już tylko szablonem, jest to specjalny szablon, ponieważ jest zależny od' HasBar' (który został już wydedukowany). Dlatego "nie jest tak ogólny" jak po prostu "CodeType", który może być wszystkim. Zmieszany? Prawdopodobnie pogarsza to ... –

+0

@AdiShavit uproszczona metoda, której używam, to wyobrazić sobie, ile "pracy" musi wykonać kompilator, aby ustalić, czy argumenty są zgodne z typami szablonów. Jeśli w meczu jest "wątpliwość/niejednoznaczność", będzie to wymagać więcej "pracy", aby dowiedzieć się, jaki typ ... Więcej "praca" oznacza mniej wyspecjalizowaną. –

Powiązane problemy