2015-09-08 12 views
6

Sto provando a definire un test SFINAE has_ostream_operator<T> per verificare se posso cout un determinato tipo. Ho funzionato, ma solo se nella mia definizione di has_ostream_operator chiamo operator<< come metodo piuttosto che come operatore infisso. In altre parole questo funziona:Perché devo chiamare l'operatore << come metodo per SFINAE per lavorare con void_t?

decltype(std::declval<std::ostream>().operator<<(std::declval<T>()))>

Questo non lo fa:

decltype(std::declval<std::ostream>() << std::declval<T>())>

caso di prova di seguito (può anche vedere di http://coliru.stacked-crooked.com/a/d257d9d6e0f3f6d9). Nota che ho incluso una definizione di void_t dato che sono solo su C++ 14.

#include <iostream> 

namespace std { 

    template<class...> 
    using void_t = void; 

} 

template<class, class = std::void_t<>> 
    struct has_ostream_operator : std::false_type {}; 

template<class T> 
struct has_ostream_operator< 
    T, 
    std::void_t< 
     decltype(
      std::declval<std::ostream>().operator<<(std::declval<T>()))>> 
    : std::true_type {}; 

struct Foo {}; 

template<class X> 
    void print(
     const X& x, 
     std::enable_if_t<has_ostream_operator<X>::value>* = 0) 
{ 
    std::cout << x; 
} 

template<class X> 
    void print(
     const X&, 
     std::enable_if_t<!has_ostream_operator<X>::value>* = 0) 
{ 
    std::cout << "(no ostream operator<< implementation)"; 
} 

int main() 
{ 
    print(3); // works fine 
    print(Foo()); // this errors when using infix operator version 
    return 0; 
} 
+0

Tale definizione di 'void_t' induce un comportamento indefinito ([namespace.std]/1). – Columbo

+0

Sei sicuro di voler risolvere questo problema per convertire un errore in fase di compilazione in un errore di runtime? Sono molto curioso del vero caso d'uso. Il tuo obiettivo è davvero essere in grado di scrivere 'print (x)' per qualsiasi 'x' e mostrare il messaggio di runtime quando' x' risulta non essere stampabile? – JorenHeit

risposta

9

Sto assumendo la versione "infisso" usato questa espressione:

std::declval<std::ostream>() << std::declval<T>() 

Il motivo che corrisponde a Foo è dovuto al fatto che la prima parte, declval<ostream>() produce un valore di tipo ostream&&. Questo corrisponde a un terzo operator<<:

template< class CharT, class Traits, class T > 
basic_ostream< CharT, Traits >& operator<<(basic_ostream<CharT,Traits>&& os, 
              const T& value); 

che sovraccaricano inoltra semplicemente la chiamata lungo:

Chiamate l'operatore di inserimento appropriato, dato un riferimento rvalue a un oggetto flusso di uscita (equivalente a os << value).

Si dovrebbe invece controllare direttamente. Tutti i sovraccarichi prendono un ostream da lvalue di riferimento, così si dovrebbe verificare anche che:

std::declval<std::ostream&>() << std::declval<T>() 
6

È necessario

std::declval<std::ostream&>() << std::declval<T>() 
//      ^

std::declval<std::ostream>() è un rvalue; si sta verificando un sovraccarico di tipo operator<< per i flussi di rvalue.

+1

Considereresti il ​​fatto che esiste un tale difetto generale (una 'std :: function' originariamente costruibile con qualsiasi cosa)? – Barry

+0

@Barry Sospetto che non sia stato un problema prima di 'void_t', ma al giorno d'oggi ... potrebbe valere un problema LWG. –

+0

Prima c'era C++ 11, poi c'era C++ void_t. Presto ci sarà C++ is_detected. – Barry

4

Se si utilizza la notazione infissa, viene trovato l'inseritore del flusso di valore, dal momento che declval restituisce rvalue di per sé; [Ostream.rvalue]:

template <class charT, class traits, class T> 
basic_ostream<charT, traits>& operator<<(basic_ostream<charT, traits>&& os, const T& x); 

Questo overload accetta attualmente tutti gli argomenti per x. Ho inviato LWG #2534, che, se risolto di conseguenza, farà funzionare il codice iniziale come previsto.

Una soluzione temporanea è di fare ritorno declval un riferimento lvalue, cioè regolare l'argomento di template a uno:

std::declval<std::ostream&>() << std::declval<T>() 
Problemi correlati