2012-02-14 12 views
7

devo funzioni per convertire diversi tipi aritmetici per mezzo precisione floating point (solo un uint16_t al livello più basso) e hanno funzioni differenti per interi e galleggianti tipi di origine punto, utilizzando SFINAE e std::enable_if :SFINAE differenziazione tra firmato e unsigned

template<typename T> 
uint16_t to_half(typename std::enable_if< 
       std::is_floating_point<T>::value,T>::type value) 
{ 
    //float to half conversion 
} 

template<typename T> 
uint16_t to_half(typename std::enable_if< 
       std::is_integral<T>::value,T>::type value) 
{ 
    //int to half conversion 
} 

Questi sono chiamati internamente da un costruttore su modelli universali per esemplificazione esplicito:

template<typename T> 
half::half(T rhs) 
    : data_(detail::conversion::to_half<T>(rhs)) 
{ 
} 

Questo compila e funziona anche bene. Ora cerco di distinguere tra numeri interi con e senza segno, sostituendo la seconda funzione, con le due funzioni:

template<typename T> 
uint16_t to_half(typename std::enable_if<std::is_integral<T>::value && 
       std::is_signed<T>::value,T>::type value) 
{ 
    //signed to half conversion 
} 

template<typename T> 
uint16_t to_half(typename std::enable_if<std::is_integral<T>::value && 
       std::is_unsigned<T>::value,T>::type value) 
{ 
    //unsigned to half conversion 
} 

Ma una volta che provo a compilare questo VS2010 mi dà

errore C2995: "uint16_t math::detail::conversion::to_half(std::enable_if<std::tr1::is_integral<_Ty>::value && std::tr1::is_signed<_Ty>::value, T>::type)": la funzione modello già definito.

Quindi sembra che non possa disambiguare tra i due modelli, ma ovviamente non ha avuto problemi con la versione integrale a fianco della versione in virgola mobile.

Ma dal momento che non sono tanto un mago di modelli, potrei semplicemente mancare qualcosa di ovvio qui (o forse dovrebbe funzionare davvero ed è solo un bug VS2010). Quindi, perché non funziona e come può essere fatto funzionare con il minor numero possibile di programmazioni possibili e nei limiti delle funzionalità solo standard (se possibile)?

+1

Non è chiaro che 'is_signed' /' is_unsigned' si escludono a vicenda (ciao 'char'?). Prova a fare in modo che la seconda versione dica '! Std :: is_signed :: value'. –

+0

Puoi provare a usare 'std :: is_signed :: value' per uno dei membri e'! Std :: is_signed :: value' per l'altro? Questo è solo per assicurarsi che non ci sia solo un tipo che ha impostazioni incoerenti per 'is_signed' e' is_unsigned'. –

+0

@KerrekSB e Dietmar Hah, l'ha fatto! Non posso credere che sia stato così facile. Se qualcuno lo aggiunge come risposta, lo accetto. –

risposta

3

Se questo non funziona, il compilatore è in errore.

Due espressioni che coinvolgono parametri del modello sono considerate equivalenti se due definizioni di funzioni che contengono le espressioni avrebbero rispettare questa regola una sola definizione ...

questa è la regola più importante da considerare qui (lasciati fuori i dettagli di "..."). I tuoi due modelli non soddisfano l'ODR perché le loro sequenze di token sono diverse.

Due modelli di funzione sono equivalenti se sono dichiarati nello stesso ambito, hanno lo stesso nome, hanno elenchi di parametri modello identici, e hanno tipi restituiti e liste di parametri che sono equivalenti utilizzando le norme di cui sopra per confrontare espressioni che coinvolgono parametri del modello.

Così i due modelli definiscono modelli diversi e non si scontrano. Ora puoi verificare se i tuoi modelli sono forse "funzionalmente equivalenti". Sarebbero se per qualsiasi possibile insieme di argomenti del modello, l'espressione enable_if restituisca sempre lo stesso valore. Ma poiché ciò non è vero per is_unsigned e is_signed, anche questo non è il caso. Se lo fosse, allora il tuo codice sarebbe mal formato, ma senza richiedere una diagnosi (che in effetti significa "comportamento indefinito").

+0

Sostituire 'is_unsigned' con'! Is_signed' (o viceversa) ha funzionato, quindi suppongo (anche se non sono così esperto nelle profondità delle specifiche del linguaggio, per non parlare dei template) che c'è un tipo che è sia firmato e ha firmato O potrebbe essere perché le versioni predefinite di questi modelli valutano entrambi i tipi 'false' per i tipi non aritmetici? Ma poi di nuovo, c'è anche il 'is_integral' per disambiguare le cose (per i tipi integri si escludono a vicenda, non dovrebbe?). –

+0

@Christian Non ho idea di cosa stiano facendo, ma questo è sicuramente un comportamento sbagliato. Prova '!! is_unsigned' invece di'! Is_signed'. Non sarei sorpreso di vederlo "funzionante" :) –

+0

Hah, anche quello ha funzionato. Ok ora penso che diventi davvero un po 'ridicolo. Probabilmente hai ragione sul fatto che il compilatore è in colpa qui. –

7

Personalmente, vorrei evitare SFINAE qui il più possibile, in quanto si può realizzare la stessa cosa con sovraccaricando:

template<typename T> 
uint16_t to_half_impl(T val, std::true_type, std::true_type) 
{ 
    // is_integral + is_signed implementation 
} 

template<typename T> 
uint16_t to_half_impl(T val, std::true_type, std::false_type) 
{ 
    // is_integral + is_unsigned implementation 
} 

template<typename T> 
uint16_t to_half_impl(T val, std::false_type, std::true_type) 
{ 
    // is_floating_point implementation 
} 

template<typename T> 
typename std::enable_if<std::is_arithmetic<T>::value, uint16_t>::type to_half(T val) 
{ 
    return to_half_impl(val, std::is_integral<T>(), std::is_signed<T>()); 
} 
+0

+1 Bella alternativa. Ma come nota a margine credo che l'ultimo argomento della versione float dovrebbe essere di 'std :: true_type', dato che i float sono sempre firmati (almeno le solite implementazioni, per i non-IEEE il mio codice di conversione non funzionerà comunque). –

+0

@Christian: sei completamente corretto; Stavo basando la logica sui documenti MSDN per 'is_signed', che capita di essere sbagliato (sorpresa, sorpresa). Fisso. – ildjarn

1

L'idioma più comune è quello di utilizzare SFINAE sul tipo di ritorno piuttosto che l'argomento genere. Altrimenti, il tipo di modello T non può essere dedotto. Con

// from C++14 
template<bool C, typename T> using enable_if_t = typename std::enable_if<C,T>::type; 

template<typename T> 
enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, uint16_t> 
to_half(T value) 
{ 
    //signed to half conversion 
} 

template<typename T> 
enable_if_t<std::is_integral<T>::value && !std::is_signed<T>::value, int16_t> 
to_half(T value) 
{ 
    //unsigned to half conversion 
} 

il tipo T nella seguente dichiarazione

auto y=to_half(x); // T is deduced from argument, no need for <T> 

è deducibile (anche banalmente), ma per il vostro codice originale non lo è! In effetti, quando si esegue questa affermazione con il to_half() attuazione attraverso clang dà

test.cc:24:11: error: no matching function for call to 'to_half' 
    auto x= to_half(4); 
      ^~~~~~~ 
test.cc:7:10: note: candidate template ignored: couldn't infer template argument 'T' 
uint16_t to_half(typename std::enable_if<std::is_integral<T>::value && 
     ^
test.cc:15:10: note: candidate template ignored: couldn't infer template argument 'T' 
uint16_t to_half(typename std::enable_if<std::is_integral<T>::value && 
     ^

Naturalmente, se si prevede esplicitamente l'argomento modello (come avete fatto), questo problema non compare. Quindi il tuo codice non era sbagliato (ma il compilatore), ma qual è il punto di SFINAE se passi il tipo di argomento del template?

+0

Non dovrebbe avere gli stessi problemi di ambiguità di SFINAE nel tipo di argomento? C'è qualche regola che rende questo lavoro, ma la versione argomento non? Altrimenti, non sembra che la domanda sia rivolta così tanto. –

+0

C'è un ** ottimo motivo ** per non utilizzare SFINAE sul tipo di argomento, vedere la risposta modificata. – Walter

Problemi correlati