2015-09-08 18 views
6

私はhas_ostream_operator<T> SFINAEテストを定義して、指定されたタイプを取り除くことができるかどうかを確認しようとしています。私はそれを働かせているが、私の定義がhas_ostream_operatorの場合にのみ、埋め込み演算子ではなくメソッドとしてoperator<<を呼び出す。換言すれば、この作品:SFINAEがvoid_tで動作するためのメソッドとしてoperator <<を呼び出さなければならないのはなぜですか?

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

これしない:以下

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

テストケース(またhttp://coliru.stacked-crooked.com/a/d257d9d6e0f3f6d9で見ることができます)。 void_tの定義は、私がC++ 14でしか使っていないので、含まれています。あなたは

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

std::declval<std::ostream>()が右辺値である必要

#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

'void_t'のその定義は未定義の動作を誘導する([namespace.std]/1)。 – Columbo

+0

これ以外のコンパイル時エラーをランタイムエラーに変換するには、このすべての問題を解決してもよろしいですか?私は実際の使用事例について非常に興味があります。あなたの目標は実際には 'x 'の' print(x) 'を書くことができ、' x'が印刷可能でないことが判明したときにランタイムメッセージを表示させることですか? – JorenHeit

答えて

9

私はあなたの「中置」バージョンを想定しています。この式を使用:

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

Fooと一致する理由は、最初の部分declval<ostream>()がの右辺値を生成するためです。これは、非部材operator<<に一致する:(os << valueに相当)の出力ストリームオブジェクトへの右辺値参照所与、

は、適切な挿入オペレータコール:過負荷

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

は単純に沿ってコールを転送します。

代わりに直接確認する必要があります。すべてのオーバーロードは、あなたがあまりにもそれをテストする必要がありますので、ostream左辺値による参照を取る:

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

。あなたは値のストリームのキャッチオールoperator<<のオーバーロードを打つことになります。

+1

このようなキャッチオールの標準的な欠陥があることを考えてみてください(これは元々何とか構成可能です)。 – Barry

+0

@Barry私はそれが本当に 'void_t'の前では問題ではなかったと思っていますが、最近では.... LWGの問題に価値があるかもしれません。 –

+0

最初にC++ 11でした。次にC++ void_tがありました。すぐにC++が検出されます。 – Barry

4

埋め込み表記を使用すると、declvalはrvalues自体を返しますので、rvalueストリームインサータが見つかります。 [ostream.rvalue]:

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

このオーバーロードは、現在xのためのすべての引数を受け入れます。私はLWG #2534を提出しました。それに応じて、あなたの初期コードは期待通りに動作します。

一時的な回避策がdeclval作る左辺値参照を返す、つまり1にテンプレート引数を調整することです:

std::declval<std::ostream&>() << std::declval<T>() 
関連する問題