2013-06-15 11 views
10

私はstd :: cout用のスレッドセーフラッパーを作成しようとしていましたが、いくつかのバリデーショナルなテンプレートを私に教えてもらいました。 ストリームマニピュレータをバリデーションテンプレート関数に渡すためのトリックはありますか?

Like that.

はその後、ちょうど私が私はそれがのstd :: ENDLでは動作しません気づいた、私は右のそれを得たと思ったとき。

このコードを取る:あなたはそれをコンパイルしようとすると

template <typename... P> 
void f(P...){} 

int main() 
{ 
    f(1,2,3,std::endl); 
} 

、GCCは非常に愚かな方法で文句を言う:

main.cpp:18:19: error: too many arguments to function 'void f(P ...) [with P = {}]' 

あなたが定期的にテンプレートを使用して、あなたが

を得ることをしようとすると
main.cpp:22:13: error: no matching function for call to 'f(<unresolved overloaded function type>)' 

実際には意味があります。

私にとっては大きな問題ではありませんが、他の方法でやってもいいですが、その制限を回避する方法があるかどうか本当に知りたいのです。

答えて

10

std::endlのようなマニピュレータは機能テンプレートです。したがって、その関数テンプレートのどの特殊化を渡す必要があるかを明示的に指定する必要があります(そうでなければ、型控除は不可能です)。例えば

:代わりに、明示的なテンプレート引数の

f(1, 2, 3, &std::endl<char, std::char_traits<char>>); 
//     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
14

アンディ・プロールにより示唆されるように、私はテンプレート引数控除をお勧めします:

C:\Temp>type meow.cpp 
#include <iostream> 
#include <utility> 
using namespace std; 

void Print() { } 

template <typename T, typename... Rest> void Print(T&& t, Rest&&... rest) { 
    cout << forward<T>(t); 
    Print(forward<Rest>(rest)...); 
} 

int main() { 
    ostream& (* const narrow_endl)(ostream&) = endl; 

    Print("Hello, world!", narrow_endl, "I have ", 1729, " cute fluffy kittens.", 
     static_cast<ostream& (*)(ostream&)>(endl) 
    ); 
} 

C:\Temp>cl /EHsc /nologo /W4 /MTd meow.cpp 
meow.cpp 

C:\Temp>meow 
Hello, world! 
I have 1729 cute fluffy kittens. 

N3690 13.4 [over.over]というルールを指定しますここではC++ 98に戻っています。基本的には、オーバーロードされた関数やテンプレート関数のアドレスを取ることは一般的にあいまいですが、特定のコンテキストでは許可されます。初期化とstatic_castingは、曖昧さを解消するのに十分な型情報を提供するため、これらのコンテキストの2つです。これにより、テンプレート引数の控除を正常に進めることができます。

明示的なテンプレート引数は非常に魅力的ですが、あらゆる種類の方法で爆発する可能性があります。明示的なテンプレート引数を壊すような方法でstd :: endlが変更されることはまずありませんが、私は本当にそれらに対して推奨します(forwardとmake_sharedのようなものが特別に設計されている場合を除いて)。

+0

したがって、narrow_endlのようなものではなく、通常の名前で修飾語を使用できるようにする方法はありませんか? – user697683

+0

@ user697683 ostreamをラッピングする印刷機能を作成する場合は、書式設定のためのラッパー機能を導入することもできます(つまり、インターフェイスにostreamマニピュレータを使用せず、代替手段を提供することもできます)。しかし、あなたはo printの上に実装されたいくつかのprintfで終わるでしょう。これはおそらくC printfの上に実装されています。 – rubenvb

2

template機能は機能ではなく、std::endlの機能はtemplateです。

template関数を渡すことはできません。ただし、オーバーロードセットを表す関数オブジェクトを渡すことはできます。

struct endl_overloadset { 
    template<typename... Args> 
    auto operator()(Args&&...args)const 
    ->decltype(std::endl(std::forward<Args>(args))) 
    { return (std::endl(std::forward<Args>(args))) }; 

    template<typename T,typename=typename std::enable_if<\ 
    std::is_same< decltype(static_cast<T>(std::endl)), T >::value\ 
    >::type>\ 
    operator T() const { return std::endl; } 
}; 

が、私はそれがあまりにも多くの決まり文句のようなビットであることがわかりましたので、あなたのために仕事をするいくつかのマクロを書く:このようファンクタを書くのはとても簡単です

#define RETURNS(X) ->decltype(X) { return (X); } // C++11 lacks non-lambda return value deduction 
#define OVERLOAD_SET(F) struct {\ 
    template<typename... Args>\ 
    auto operator()(Args&&...args)const\ 
    RETURNS(F(std::forward<Args>(args)...))\ 
    template<typename T,typename=typename std::enable_if<\ 
    std::is_same< decltype(static_cast<T>(F)), T >::value\ 
    >::type>\ 
    operator T() const { return F; }\ 
} 
static OVERLOAD_SET(std::endl) Endl; 

その後、Endlを渡しますfに送信し、Endl(Blah)と電話するとstd::endl(Blah)になります。同様に、Endlを変数に代入するか、またはメソッドに渡すことは、基本的には変数にstd::endlを代入するか、またはメソッドに渡します(オーバーロードの分解能)。

悲しいことに、ローカルタイプはtemplateメソッドを持つことができないため、OVERLOAD_SETは関数内で使用できません。関数内で使用できる場合は、

f(1,2,3, OVERLOAD_SET(std::endl)()); 

となります。しかし、それはあなたがプログラムしたい言語であり、あなたが持っている言語ではありません。 (さらに、マクロに頼るのではなく、[]構文の乱暴な乱用を使用して自動的に過負荷設定ファンクタを生成できるようにする@ Xeoの提案が良いでしょう)。

Live exampleここで私は自分のendl_functorprintという方法に渡してから、<<をそれ以上利用することなく使用します。

+0

このような変形されたマニピュレータは、実際のものと同じように使用することはできませんでした。 'std :: cout << OVERLOAD_SET(std :: endl){};'(あるいは 'std :: cout << [std: :endl; ')はコンパイルされません。なぜなら、これらの2つは通常の関数を生成するのではなく、関数オブジェクトを生成するからです。 – Xeo

+0

@xeo hmm: '演算子T 'を追加すると動作するかもしれません。 – Yakk

+0

@xeo「演算子T」を追加してテストしました! 'endl_functor'は' std :: endl'が変換するものと同じものに変換されます。 – Yakk

関連する問題