2016-05-17 31 views
5

Il seguente codice funziona correttamente in VS2015:Template Selezione Funzione Sulla base di tipo annidato

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

stampa:

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

Tuttavia, it does not compile su gcc/clang, lamentandosi:

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] 

L'ambiguità viene risolta correttamente da VS2015 (che non significa che sia la cosa conforme a d o).

Come posso ottenere questo per compilare ed eseguire correttamente su Clang/gcc?
Ho pensato di utilizzare std::enable_if ma non riuscivo a farlo fare quello che voglio (probabilmente l'ho usato in modo errato). Se questa è la strada da percorrere, come dovrebbe essere usato per risolvere questa ambiguità?

UPDATE:
Aggiunta typename HasBar :: Bar per il modello params ottiene gcc/Clang per costruire ed eseguire il codice correttamente:

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

Questo sembra dire al compilatore che c'è un secondo , valore del parametro modello non di tipo (non utilizzato nel codice funzione) di tipo typename HasBar::Bar. If typename HasBar::Bar non esiste, SFINAE rimuoverà questa funzione dal set di sovraccarico e verrà selezionato il modulo generico.

Tuttavia, quando esiste, non so perché questa funzione avrà la precedenza sul primo. Immagino perché è più specializzato, anche se la specializzazione non è usata nel codice stesso. Tuttavia, in questo caso, era già più specializzato anche prima del nuovo param!

Tuttavia, in questo caso VS2015 sceglie sempre la forma generica dare la risposta sbagliata!

C'è qualche sintassi (e/o soluzione) che funzionerà in tutti i casi?

+0

Come hai usato 'enable_if'? Lo hai usato per abilitare il secondo sovraccarico o * disabilitare * il primo sovraccarico? –

+0

In realtà ho provato varie cose ma non sono sicuro che fossero corrette. Come * dovrebbe * essere usato qui? –

+1

Mi sarei aspettato che la seconda funzione fosse più specializzata e quindi fosse il sovraccarico preferito quando applicabile (quindi VS avrebbe ragione). Qualcuno può commentarlo? – MikeMB

risposta

2

lo farei con SFINAE (https://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error) trucco in un modo simile a questo:

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

Per VS2015, immagino che ha un bug con ricerca del nome a due fasi (What exactly is "broken" with Microsoft Visual C++'s two-phase template instantiation?).

+0

Funziona, ma c'è un modo meno verboso, più generico per farlo? Forse con più 'std' utils? –

+0

Ho finito usando questo dato che è l'unica cosa che ha funzionato in modo coerente, anche su VS2015. –

4

13 minuti dopo ... rispondendo a me stesso. [NOT] Risolto!

Aggiunta typename HasBar::Bar al modello 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; 
} 

Questo sembra dire al compilatore che c'è un secondo, non di tipo valore del parametro di modello (non utilizzato nel codice di funzione), che è di tipo typename HasBar::Bar. Se typename HasBar::Bar non esiste, SFINAE rimuoverà questa funzione dal set di sovraccarico e verrà selezionato il modulo generico.

Tuttavia, quando esiste , non so perché questa funzione avrà la precedenza sul primo. Immagino perché è più specializzato, anche se la specializzazione non è usata nel codice stesso.
Tuttavia, in questo caso, era già più specializzato anche prima del nuovo param!

+0

Puoi (o qualcun altro) spiegare anche perché funziona così? Le citazioni standard sarebbero benvenute. –

+0

@ TamásSzelei: Utilizza SFINAE per rimuovere la funzione inserita dal set di overload, se HasBar :: Bar non è valido. (Come in "if" HasBar' non ha un tipo nidificato "Bar") – MikeMB

+0

Questo risulta , infatti, corregge gcc/clang ma rompe VS2015 - causando una risposta errata :-( –

3

Rispondendo alla domanda sul perché ...

Un modo semplice per capire questo è da fare manualmente quello che il compilatore avrebbe fatto.

Dato:

// 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); 

sostituendo lambda, int:

prima forma: template<lambda, int> void funky(int, lambda) - fine, richiesti due sostituzioni

seconda forma: template<lambda, lambda::Bar> < - errore SFNAE qui perché lambda non ha un tipo subordinato Bar.

Pertanto la prima forma è scelto (essendo l'unico legale)

sostituendo HasBar, HasBar :: Bar:

prima forma: template<HasBar, HasBar::Bar> void funky(HasBar::Bar, HasBar) - bene, tenuti due sostituzioni

secondo modulo: template<HasBar, HasBar::Bar> < - Fine, necessario zero sostituzioni.

Entrambi sono validi, ma il secondo modulo richiede un numero inferiore di sostituzioni del modello. È quindi più specializzato e una corrispondenza migliore.

in risposta alle domande:

ok ... lascia pensare a un modo che potremmo implementare l'algoritmo template match in un compilatore ...

Al punto di questa chiamata 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

Due cose: (1) Significa che la selezione non prende effettivamente in considerazione i parametri effettivi per il set di sovraccarico, perché quello già contenuto ' typename HasBar :: Bar'? (2) Puoi approfondire cosa intendi per sostituzione?Perché il secondo modulo è nel secondo caso 0 sostituzioni? Puoi specificare esplicitamente le sostituzioni nella risposta? –

+0

@AdiShavit I tipi testati rispetto agli argomenti del modello sono dedotti dai tipi di argomenti nel sito di chiamata. Nel secondo modulo, HasBar è un argomento modello (vale a dire deve essere abbinato), ma una volta fatto, 'HasBar :: Bar' non è più solo un tipo di modello, è un tipo di modello speciale perché dipende da' HasBar' (che è già stato dedotto). È quindi "non così generico" come semplicemente "CodeType", che potrebbe essere qualsiasi cosa. Confuso? Probabilmente sto peggiorando le cose ... –

+0

@AdiShavit il metodo semplicistico che uso è immaginare quanto "lavoro" deve fare il compilatore per capire se gli argomenti corrispondono ai tipi di template. Se c'è "dubbio/ambiguità" su una partita, richiederà più "lavoro" per capire il tipo ... Più "lavoro" significa meno specializzato. –