2016-05-27 9 views
16

Ich versuche, eine Typ-Traits-Klasse zu erstellen, um festzustellen, ob ein bestimmter Typ T über den <<-Operator von std::ostream gestreamt werden kann. Ich verwende eine einfache SFINAE-Technik.Declval Ausdruck (für SFINAE) mit Std :: Ostream

Letztlich ist der Ausdruck I für die Substitution Versagen zu bewerten versuchen, ist:

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

Meine Erwartung ist, dass eine Instanz t vom Typ gegeben T und eine std::ostream Instanz os, wenn der Ausdruck os << t ist schlecht ausgebildet , sollte ein Substitutionsfehler auftreten.

Aber offensichtlich tritt Substitutionsfehler hier nie auf, unabhängig vom Typ T. Und selbst wenn ich nur mit dem obigen Ausdruck decltype außerhalb des Kontextes von SFINAE deklariere, kompiliert es glücklich, auch wenn T nicht mit std::ostream verwendet werden kann.

Zum Beispiel:

struct Foo { }; 

int main() 
{ 
    // This compiles fine using GCC 4.9.2 
    // 
    typedef decltype(
     std::declval<std::ostream>() << std::declval<Foo>() 
    ) foo_type; 
} 

Das oben kompilieren feine GCC 4.9.2 verwenden, das ist nicht das, was ich erwartet habe, da die << Betreiber nicht überlastet wird Foo mit einer Art zu arbeiten. Und natürlich, wenn ich sage:

std::cout << Foo(); 

... Ich bekomme einen Compiler-Fehler. Warum also kompiliert der decltype Ausdruck überhaupt überhaupt?

+0

Es reagiert richtig mit 'std :: declval ()'. Aber ich habe keine Ahnung warum. – Quentin

Antwort

15

C++ 11 addierten folgende operator<< Überlastung:

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

Dieser leitet den Standardeinführungs Operatoren auf, die nicht rvalue Verweise auf std::ostream s binden können, weil sie nicht-const Referenzen nehmen. Da std::declval<std::ostream>std::ostream&& zurückgibt, wird diese Überladung ausgewählt, und aufgrund der sehr permissiven Schnittstelle (d. H. Dies ist nicht SFINAEd out, wenn es keinen gültigen Insertionsoperator gibt) funktioniert Ihr decltype Spezifizierer.

Die einfache Lösung ist std::declval<std::ostream&>() zu verwenden.Dies wird ein std::ostream& zurückkehren, so dass die Vorlage Überlastung wird von Ihrem decltype Spezifizierer und einer normalen Einfügeoperator Überlastung nicht erforderlich sein wird, ausgewählt werden, um es zu kompilieren:

main.cpp:8:39: error: invalid operands to binary expression ('std::basic_ostream<char>' and 'Foo') 
     std::declval<std::ostream&>() << std::declval<Foo>() 
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~ 

:

typedef decltype(
    std::declval<std::ostream&>() << std::declval<Foo>() 
) foo_type; 

Clang diese Ausgänge Live Demo


Hier ist ein einfacheres Beispiel, die das gleiche Problem zeigt:

#include <string> 

void foo (int&,int){} 
void foo (int&,float){} 

template <typename T> 
void foo (int&& a, T b) { 
    foo(a, b); 
} 

int main() 
{ 
    std::string s; 
    typedef decltype(foo(1,s)) foo_type; 
} 

Live Demo


Hier sind die einschlägigen Normen Anführungszeichen (N4140):

Die Erklärung muss instanziiert werden, da die Überladungsauflösung beteiligt ist:

[temp.inst]/10: If a function template or a member function template specialization is used in a way that involves overload resolution, a declaration of the specialization is implicitly instantiated (14.8.3).

Nur die Erklärung Bedürfnisse instanziiert werden:

[temp.over]/5: Only the signature of a function template specialization is needed to enter the specialization in a set of candidate functions. Therefore only the function template declaration is needed to resolve a call for which a template specialization is a candidate.

Und die Umsetzung ist nicht die Funktion Körper zu instanziiert erlaubt:

[temp.inst]/11: An implementation shall not implicitly instantiate a function template, a variable template, a member template, a non-virtual member function, a member class, or a static data member of a class template that does not require instantiation.

+0

Warum zeigt Ihr einfacheres Beispiel das Problem? (dh, warum ist es kein Substitutionsfehler?) – Eric

+0

@Eric Ich meine, es wäre in keinem Fall ein Substitutionsfehler, weil es nicht in einem SFINAE-Kontext ist, aber es ist kein schwerer Fehler, weil der Deklarationstyp decltype ist ein nicht evaluierter Kontext, so wird nur die Deklaration instanziiert. – TartanLlama

+0

@Eric Ich habe die relevanten Standards zitiert, ist das klarer? – TartanLlama

1

Wenn Sie auf der Header-Datei aussehen ostream, werden Sie feststellen, dass, weil std::declval rvlaue Referenzen erzeugt, es tatsächlich eine passende generisch operator<<:

#if __cplusplus >= 201103L 
    /** 
    * @brief Generic inserter for rvalue stream 
    * @param __os An input stream. 
    * @param __x A reference to the object being inserted. 
    * @return os 
    * 
    * This is just a forwarding function to allow insertion to 
    * rvalue streams since they won't bind to the inserter functions 
    * that take an lvalue reference. 
    */ 
    template<typename _CharT, typename _Traits, typename _Tp> 
    inline basic_ostream<_CharT, _Traits>& 
    operator<<(basic_ostream<_CharT, _Traits>&& __os, const _Tp& __x) 
    { 
     __os << __x; 
     return __os; 
    } 
#endif // C++11 

Dies erklärt, warum Sie kein Substitutions Scheitern. Dies kann jedoch nicht tatsächlich mit dem std::cout << Foo() Aufruf verglichen werden. Hier ist der relevante Teil des Übersetzungsfehlers:

/usr/local/bin/../lib/gcc/x86_64-pc-linux-gnu/6.1.0/../../../../include/c++/6.1.0/ostream:628:5: note: candidate function [with _CharT = char, _Traits = std::char_traits<char>, _Tp = Foo] not viable: no known conversion from 'ostream' (aka 'basic_ostream<char>') to 'basic_ostream<char, std::char_traits<char> > &&' for 1st argument 
    operator<<(basic_ostream<_CharT, _Traits>&& __os, const _Tp& __x) 
    ^

Das Problem hierbei ist, dass die linke Skala nur eine rvalue Referenz sein kann, aber Sie sind (natürlich) mit einem L-Wert (das heißt std::cout) in dem Aufruf.

2

antwortet nicht wirklich, warum dies geschieht, aber wenn Sie mit std::stream& wie unten ersetzen:

template<typename T, typename Enable = std::ostream&> 
struct can_be_streamed : std::false_type {}; 
template<typename T> 
struct can_be_streamed<T, 
     decltype(std::declval<std::ostream&>() << std::declval<T>())> : std::true_type {}; 

scheint zu funktionieren.

Live Demo