Usando SFINAE per verificare l’operatore globale <<?

Voglio avere diverse funzioni globali to_string() sovraccaricate che prendono un tipo T e lo convertono nella sua rappresentazione di stringa. Nel caso generale, voglio essere in grado di scrivere:

 template inline typename enable_if<!std::is_pointer::value && has_insertion_operator::value, void>::type to_string( T const &t, OutputStringType *out ) { std::ostringstream o; o << t; *out = o.str(); } 

La mia implementazione di has_insertion_operator finora è:

 struct sfinae_base { typedef char yes[1]; typedef char no[2]; }; template struct has_insertion_operator : sfinae_base { template static yes& test( U& ); template static no& test(...); static std::ostream &s; static T const &t; static bool const value = sizeof( test( s << t ) ) == sizeof( yes ); // line 48 }; 

(Prende in prestito da questo e da questo .) Sembra funzionare. Ma ora voglio avere una versione sovraccaricata di to_string per i tipi che non hanno operator<< ma hanno la loro funzione membro to_string() , cioè:

 template inline typename enable_if<!has_insertion_operator::value && has_to_string::value, void>::type to_string( T const &t, OutputStringType *out ) { *out = t.to_string(); } 

L’implementazione di has_to_string è:

 #define DECL_HAS_MEM_FN(FN_NAME) \ template \ struct has_##FN_NAME : sfinae_base { \ template struct type_check; \ template static yes& test(type_check*); \ template static no& test(...); \ static bool const value = sizeof( test(0) ) == sizeof( yes ); \ } DECL_HAS_MEM_FN( to_string ); 

(Questa parte sembra funzionare bene. È adattata da questo .) Tuttavia, quando ho:

 struct S { string to_string() const { return "42"; } }; int main() { string buf; S s; to_string( s, &buf ); // line 104 } 

Ottengo:

 foo.cpp: In instantiation of 'const bool has_insertion_operator::value': foo.cpp:104: instantiated from here foo.cpp:48: error: no match for 'operator<<' in 'has_insertion_operator::s << has_insertion_operator::t' 

Sembra che la SFINAE non stia succedendo. Come scrivo has_insertion_operator correttamente in modo tale da determinare se un operator<< globale operator<< è disponibile?

A proposito: sto usando g ++ 4.2.1 (quello che viene fornito come parte di Xcode su Mac OS X). Inoltre, mi piacerebbe che il codice fosse solo C ++ 03 standard senza librerie di terze parti, ad esempio, Boost.

Grazie!

Sarei dovuto essere semplicemente più fedele a questa risposta. Un’implementazione funzionante è:

 namespace has_insertion_operator_impl { typedef char no; typedef char yes[2]; struct any_t { template any_t( T const& ); }; no operator<<( std::ostream const&, any_t const& ); yes& test( std::ostream& ); no test( no ); template struct has_insertion_operator { static std::ostream &s; static T const &t; static bool const value = sizeof( test(s << t) ) == sizeof( yes ); }; } template struct has_insertion_operator : has_insertion_operator_impl::has_insertion_operator { }; 

Credo che in realtà non faccia affidamento su SFINAE.

L’inizializzatore di value sulla riga 48 non è in un contesto in cui funziona SFINAE. Prova a spostare l’espressione nella dichiarazione della funzione.

 #include  struct sfinae_base { typedef char yes[1]; typedef char no[2]; }; template struct has_insertion_operator : sfinae_base { // this may quietly fail: template static yes& test( size_t (*n)[ sizeof( std::cout << * static_cast(0) ) ] ); // "..." provides fallback in case above fails template static no& test(...); static bool const value = sizeof( test( NULL ) ) == sizeof( yes ); }; 

Tuttavia, devo mettere in discussione la quantità di sofisticazione che sta accadendo in questo. Vedo meccanismi non ortogonali che si to_string uno contro l’altro ( to_string vs. operator<< ) e sento che le ipotesi sbagliate vengono sballottate (ad esempio, l' operator<< è globale rispetto a un membro, anche se il codice implementato sembra OK in quel considerare).