2016-05-27 15 views
16

std::ostream<<演算子で特定のタイプのTをストリーミングできるかどうかを判断するために、型特性クラスを作成しようとしています。私は簡単なSFINAE技術を使用しています。std :: ostreamを使用した(SFINAEの)式の表現

最終的に、私は代替の障害のために評価しようとする表現がされて:式os << tが悪い形成されている場合私の期待は、タイプTstd::ostreamインスタンスosのインスタンスt与えられた、ということである

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

置換の失敗が発生するはずです。

明らかに、置換の失敗は、タイプTに関係なくここでは発生しません。そして、私がちょうど上記のdecltype式を使ってtypedefを宣言したとしても、SFINAEの文脈の外では、でTを使用することはできません。例えば

struct Foo { }; 

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

は上記<<オペレータがタイプFooで動作するようにオーバーロードされていないので、私が期待したものではないGCC 4.9.2を使用して微細なコンパイルされます。もちろん、私が言うならば:

std::cout << Foo(); 

...私はコンパイラエラーを取得します。だから、なぜ上記のdecltypeの式はまったくコンパイルされますか?

+0

それは 'のstd :: declval <はstd :: ostreamに&>()'と正しく反応します。しかし、なぜ私は考えていない。 – Quentin

答えて

15

C++ 11には、以下のoperator<<過負荷を追加しました:

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

これは、彼らが非const参照を取るためstd::ostream sの右辺値参照をバインドすることはできません標準挿入オペレータに転送します。 std::declval<std::ostream>std::ostream&&を返すため、このオーバーロードが選択されているため、非常に許容されるインターフェイス(有効な基礎となる挿入演算子がない場合はSFINAEdが出力されません)のため、decltype指定子が機能します。

単純な修正は、std::declval<std::ostream&>()を使用することです。

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; 

クランは、この出力:テンプレートの過負荷があなたのdecltype指定子と通常の挿入演算子オーバーロードによって選択されることはありませんので、それをコンパイルするためにこれが必要となり、std::ostream&を返します。 Live Demo


は、ここで同じ問題を示す単純な例です:

0123ここで
#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


は、関連する規格の引用符(N4140)は次のとおりです。

オーバーロードの解決が関与しているため、宣言はインスタンス化する必要があります。

[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).

のみ宣言ニーズインスタンス化する:

[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.

と実装は、関数本体をインスタンス化することはできません。

[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

あなたのより単純な例題がこの問題を示すのはなぜですか? (なぜ、それが置換失敗ではないのですか?) – Eric

+0

@Eric私は、SFINAEコンテキストにはないため、いずれの場合でも置換に失敗しないことを意味しますが、 'decltype'指定子は未評価のコンテキストなので、宣言だけがインスタンス化されます。 – TartanLlama

+0

@エリック私は関連する規格の引用符を追加しましたが、それは明確ですか? – TartanLlama

1

あなたはヘッダファイルostreamを見れば、あなたはstd::declvalはrvlaue参照を生成するので、マッチングジェネリックが実際に存在していることがわかります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 

ここでは、置換の失敗が発生しない理由を説明します。ただし、これは実際にはstd::cout << Foo()コールと照合することはできません。ここでは、コンパイル・エラーの関連部分は次のとおりです。

/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) 
    ^

ここでの問題は、LHSが唯一の右辺値参照できることですが、(明らかに)呼び出しで左辺値(すなわちstd::cout)を使用しています。なぜこれが起こっている

2

は本当に答えませんが、あなたは以下のようにstd::stream&と交換した場合:

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 {}; 

が動作しているようです。

Live Demo

関連する問題