2013-04-09 13 views
26

Quindi voglio scrivere un automatico !=:Come evitare questa frase è falsa in un modello SFINAE?

template<typename U, typename T> 
bool operator!=(U&& u, T&& t) { 
    return !(std::forward<U>(u) == std::forward<T>(t)); 
} 

ma che è maleducato . Così scrivo

// T() == U() is valid? 
template<typename T, typename U, typename=void> 
struct can_equal:std::false_type {}; 

template<typename T, typename U> 
struct can_equal< 
    T, 
    U, 
    typename std::enable_if< 
     std::is_convertible< 
     decltype(std::declval<T>() == std::declval<U>()), 
     bool 
     >::value 
    >::type 
>: std::true_type {}; 

che è una classe di tipo tratti che dice "è t == u codice valido che restituisce un tipo convertibile in bool".

Così ho migliorare il mio !=:

template<typename U, typename T, 
    typename=typename std::enable_if<can_equal<T,U>::value>::type 
> 
bool operator!=(U&& u, T&& t) { 
    return !(std::forward<U>(u) == std::forward<T>(t)); 
} 

ed ora è solo una sostituzione valida se == esiste. Purtroppo, è un po 'avido:

struct test { 
}; 
bool operator==(const test&, const test&); 
bool operator!=(const test&, const test&); 

come sarà snarf fino praticamente ogni test() != test() piuttosto che quanto sopra != essere chiamato. Penso che questo non sia desiderato - preferirei chiamare un esplicito != piuttosto che l'inoltro automatico a == e negare.

Così, scrivo questa classe tratti:

template<typename T, typename U,typename=void> 
struct can_not_equal // ... basically the same as can_equal, omitted 

che verifica se T != U è valido.

Abbiamo poi aumentare il != come segue:

template<typename U, typename T, 
    typename=typename std::enable_if< 
    can_equal<T,U>::value 
    && !can_not_equal<T,U>::value 
    >::type 
> 
bool operator!=(U&& u, T&& t) { 
    return !(std::forward<U>(u) == std::forward<T>(t)); 
} 

che, se si analizza, dice "questa frase è falsa" - operator!= esiste tra T e U sse operator!= non esiste tra il T e U .

Non sorprendentemente, ogni compilatore ho testato i segfaults quando li ho nutriti. (clang 3.2, gcc 4.8 4.7.2 intel 13.0.1). Sospetto che ciò che sto facendo sia illegale, ma mi piacerebbe vedere il riferimento standard. (modifica: Quello che sto facendo è illegale, perché induce un'espansione ricorsiva illimitata del modello, come determinare se il mio != si applica richiede che controlliamo se si applica il mio !=. La versione collegata nei commenti, con #if 1, dà un errore ragionevole).

Ma la mia domanda: c'è un modo per convincere la mia esclusione in base SFINAE di ignorare "si" al momento di decidere se dovesse fallire o no, o in qualche modo a sbarazzarsi del problema referenziale sé in qualche modo? O abbassare la precedenza del mio operator!= abbastanza basso così ogni != esplicita vince, anche se non è altrimenti una corrispondenza?

Quello che non verifica la presenza di "!= non esiste" funziona abbastanza bene, ma non abbastanza bene da essere altrettanto scortese da iniettarlo nello spazio dei nomi globale.

L'obiettivo è qualsiasi codice che possa essere compilato senza la mia "magia" != fa esattamente la stessa cosa una volta che viene introdotta la mia "magia" !=. Se e solo se != è altrimenti invalido, ebool r = !(a==b) è ben formato se il mio "magico" != entra.


nota : Se si crea un template<typename U, typename T> bool operator!=(U&& u, T&& t), SFINAE penserà che ogni paio di tipi ha una valida != tra di loro. Quindi quando si tenta di chiamare effettivamente !=, viene istanziato e non riesce a compilare. Inoltre, esegui il doppiaggio delle funzioni bool operator!=(const foo&, const foo&), perché hai una corrispondenza migliore per foo() != foo() e foo a, b; a != b;. Considero di fare entrambi questi scortesi.

+0

Sono sicuro che lo sapete, ma 'namespace std :: rel_ops' da' 'ha la versione pratica (e ingenua) di questo. –

+2

Scambia l'argomento del modello predefinito per un parametro non di tipo e fornisci un SSCCE da qualche parte che possa essere facilmente copiato. – Xeo

+1

+1 solo per fare cose pazzesche. SFINAE ricorsivo ... Wow :-P –

risposta

11

Il problema con il tuo approccio sembra essere che la definizione globale di fallback di operator != è troppo attraente e hai bisogno di un controllo SFINAE per escluderlo. Tuttavia, il controllo di SFINAE dipende dall'ammissibilità della funzione stessa per la risoluzione del sovraccarico, portando così a una (o tentata) ricorsione infinita durante la deduzione del tipo.

Mi sembra che qualsiasi tentativo simile basato su SFINAE si schianta contro lo stesso muro, quindi l'approccio più sano è a mio parere per rendere il vostro operator != un po 'meno attraente per la risoluzione del sovraccarico in primo luogo, e lasciare altro , ragionevolmente scritto (questo sarà chiaro tra un momento) i sovraccarichi di operator != hanno la precedenza.

Dato il tipo di tratto can_equal che hai fornito:

#include <type_traits> 
#include <functional> 

template<typename T, typename U, typename=void> 
struct can_equal : std::false_type {}; 

template<typename T, typename U> 
struct can_equal< 
    T, 
    U, 
    typename std::enable_if< 
     std::is_convertible< 
     decltype(std::declval<T>() == std::declval<U>()), 
     bool 
     >::value 
    >::type 
>: std::true_type {}; 

definirei il fallback operator != in questo modo:

template<typename T, typename U> 
bool is_not_equal(T&& t, U&& u) 
{ 
    return !(std::forward<T>(t) == std::forward<U>(u)); 
} 

template< 
    typename T, 
    typename... Ts, 
    typename std::enable_if<can_equal<T, Ts...>::value>::type* = nullptr 
    > 
bool operator != (T const& t, Ts const&... args) 
{ 
    return is_not_equal(t, args...); 
} 

Per quanto ne so, qualsiasi sovraccarico del operator != che definirà esattamente due i parametri di funzione (quindi nessun pacchetto di argomenti) saranno più adatti per la risoluzione di sovraccarico. Pertanto, la precedente versione di riserva di operator != verrà selezionata solo quando non esiste un sovraccarico migliore. Inoltre, verrà selezionato solo se il tratto di tipo can_equal<> restituirà true.

Ho provato questo contro lo SSCCE hai preparato, dove quattro struct s sono definiti insieme ad alcuni sovraccarichi di operator == e operator !=:

struct test { }; 

bool operator==(const test&, const test&) { std::cout << "(==)"; return true; } 
bool operator!=(const test&, const test&) { std::cout << "(!==)"; return true; } 

struct test2 { }; 

struct test3 { }; 
bool operator == (const test3&, const test3&) 
{ std::cout << "(==)"; return true; } 

struct test4 { }; 

template<typename T, 
     EnableIf< std::is_convertible< T, test4 const& >::value >... > 
bool operator == (T&&, T&&) { std::cout << "(==)"; return true; } 

template<typename T, 
     EnableIf< std::is_convertible< T, test4 const& >::value >... > 
bool operator != (T&&, T&&) { std::cout << "(!=)"; return true; } 

Per verificare che l'output desiderato viene prodotto e specchio quello che hai fatto nella versione originale del fallback operator !=, ho aggiunto una stampa a is_not_equal():

template<typename T, typename U> 
bool is_not_equal(T&& t, U&& u) 
{ 
    std::cout << "!"; // <== FOR TESTING PURPOSES 
    return !(std::forward<T>(t) == std::forward<U>(u)); 
} 

Qui ci sono le tre prove dal tuo ex ampia:

std::cout << (a != b) << "\n"; // #1 
std::cout << (test3() != test3()) << "\n"; // #2 
std::cout << (test4() != test4()) << "\n"; // #3 

riguardante il primo test, operator != è definito per tipo test, quindi linea #1 dovrebbe stampare:

(!==)1 

Per quanto riguarda la seconda prova, operator != è non definito per test3 e test3 non è convertibile in test4, quindi il nostro operator != globale dovrebbe entrare in gioco e annullare il risultato del sovraccarico di operator == che richiede due const test3&.Pertanto, la linea #2 deve stampare:

!(==)0 // operator == returns true, and is_not_equal() negates it 

Infine, il terzo test richiede due oggetti rvalue di tipo test4, per cui viene definita operator != (perché gli argomenti sono convertibili a test4 const&). Pertanto, la linea #3 dovrebbe stampare:

(!=)1 

Ed ecco un live example dimostrando che l'output prodotto è quello atteso.

+0

+1, molto meglio del mio approccio. – ipc

+0

@ipc: Grazie, ma ancora non sembra dare lo stesso risultato del caso di test dell'OP. O forse sto interpretando male. Come nota a margine, l'SSCCE fornito dall'OP fornisce risultati diversi con Clang e GCC, quindi mi chiedo quale compilatore si fida di –

+0

La cosa strana è che questo sembra essere legale a partire da '13.5/1' perché un parametro di funzione il conteggio conta esattamente come un parametro di funzione. Non sono sicuro se questo è previsto dallo standard. – ipc

Problemi correlati