2016-09-22 4 views
2

テンプレートコールバックパラメータを使用するときに特定のコールバックシグネチャを適用したいと考えています。テンプレートコールバックパラメータに特定のシグネチャを適用する方法

  1. メソッドがコールバックを受け入れる深くではなく、ユーザー呼び出しサイトでコンパイルエラーを取得します。
  2. 少なくとも驚きの原則を守り、コールバックを使用して実装がなど(正確な署名の試合を)その値をチェックしません値を返すコールバックを提供するからユーザーを防ぐ
  3. 利用慣用的なC++と可能な限りのベストプラクティス。効率化してください。 C#の出身

は、私が最初にそうようなアクションとのFuncクラスを模倣しようとした:

template<typename ...TArgs> using Action = std::function<void(TArgs...)>; 
template<typename TRet, typename ...TArgs> using Func = std::function<TRet(TArgs...)>; 

それは私がこれを避けるために聞いたのstd ::関数を使用除いこれは、[OK]を作品'単純な'タスク(失敗#3)。また、残念ながら上記の#2の要件を満たしていません。

void DoActionStuff(Action<int, float> callback) 
{ 
    callback(1, 2.0); 
} 

void DoFuncStuff(Func<float, int, float> callback) 
{ 
    float result = callback(1, 2.0); 
    if (result > 100.0) { throw runtime_error("derp"); }; 
} 

DoActionStuff([](int x, float y) { cout << x << " " << y << endl; }); // OK 
DoActionStuff([](int x, float y) { cout << x << " " << y << endl; return x * y; }); // No error :(Why not? 

DoFuncStuff([](int x, float y) { cout << x << " " << y << endl; }); // OK - We get a compiler error right here at the call-site for this one... 
DoFuncStuff([](int x, float y) { cout << x << " " << y << endl; return x * y; }); // OK 

3つの要件をすべて満たすにはどうすればよいですか?どのタイプのコールバックが必要なのかを正確に伝達するために、このメソッドに優れたシグネチャを提供するために使用できる、素晴らしい、特色のあるマジックがありますか?

template<typename TCallback> 
void DoStuff(TCallback callback) { ... } // Unnecessarily burdens the client of this api (they have no idea the signature to use here) 
+1

DoActionStuff([i](auto f、auto f){cout << i + f;}) 'を実行したい場合はどうすればよいですか?あなたはそれを禁止したいですか? – Barry

答えて

0

限り、あなたは非キャプチャラムダを使用すると、あなたは彼らが関数ポインタに崩壊するという事実に頼ることができます。それ以外の場合は

void doActionStuff(void(*callback)(int, double)) { callback(0, 0.); } 
int main() { doActionStuff([](int, double) { }); } 

、あなたがチェックを実施し、まだキャプチャを使用したい場合は、私はあなたの最初に固執なり、正直なところ

#include <type_traits> 
#include <utility> 

template<typename F> 
int doActionStuff(F &&f) { 
    static_assert(std::is_same<std::result_of_t<F(int, double)>, int>::value, "!"); 
    return std::forward<F>(f)(0, 0.); 
} 

int main() { 
    int i = 0; 
    doActionStuff([](int, double) { return 42; }); 
    doActionStuff([i](int, double) { return 42; }); 
    // these won't work 
    // doActionStuff([i](int) { return 42; }); 
    // doActionStuff([](int, double) { }); 
} 
+0

キャプチャとノンキャプチャの呼び出しが良好です。私はそれが署名を変えるとは思っていませんでした。今私が知っている、私はもちろんそれが欲しい:)それは潜在的にはあまり魅力的ではない関数のポインタオプションになります。本当にC++にこれを表現するうまい方法があったらいいのに... –

1

:ラムダは、あなたが戻り値の型と機能Fのプロトタイプを確認するために、あなたの関数でstatic_assertを置くことができます本能、レスタイプの別名は:

void DoAction(std::function<void(int, float)>); 
void DoFuncStuff(std::function<float(int, float)>); 

それは、これらの機能が期待呼び出しサイトではっきりと見える、彼らはすでにあなたのための有効性チェックを行います。彼らは正確にの署名をチェックしませんが、それは本当にあなたが望むものですか? DoAction()への呼び出し可能な戻り値は無視され、暗黙的な変換が引数型に対して許可されます。しかし、それはどこでもC++で常に真実です。&dagger;。また、これは一般的なラムダに渡すように、より複雑な構造を可能にする:あなたはもちろんのような何か書くことができ

DoFuncStuff([](auto i, auto f){ return f; }); // really want to disallow this? 

// function pointer case 
template <class R, class... Args> std::true_type check_member(R (*)(Args...)); 

// function object case 
template <class F, class R, class... Args> std::true_type check_member(R (F::*)(Args...)); 
template <class F, class R, class... Args> std::true_type check_member(R (F::*)(Args...) const); 
template <class R, class... Args, class F> auto check_member(F&) -> decltype(check_member<F, R, Args...>(&F::operator())); 

// failure case 
template <class... > std::false_type check_member(...); 

template <class F> 
void DoAction(F f) { 
    static_assert(
     decltype(check_member<void, int, float>(f))::value, 
     "invalid signature" 
    ); 

    // body 
} 

をしかし、それは仕方あまり明確あなたのオリジナルのアイデアよりもです。そして、オーバーロードされテンプレート化されたoperator()を正しく処理するには、check_memberのものが必要です。


&dagger;モジュロ例外は、通常どおりです。

+0

ええと、おそらく私はstd :: functionにとどまるでしょう。それは大規模な64byteの構造ですが、スタックは安いです:)私がstatic_assertを明らかにしてきた特徴を模索していた限り、私はフレーズとスペルがより良いコンパイラ出力になることを望んでいません(私は出力メッセージに署名を入れて自分自身をうまくやらなければならない)。ええ、私は 'auto'についても忘れていたと思うので、私はそれを一般的なものにする必要があるでしょう。しかし、特定のインスタンスでは戻り値を許可することについてはまだ悲しいですが、私はそれがちょうど方法だと思います。 –

+0

@Roger 'static_assert'のあなた自身のメッセージは、コンパイラが生成できるものよりはるかに明確になります。 – Barry

+0

@RogerBまた、テンプレートコードの全面的な混乱で、私はまだ間違っていると思われる 'void(int const&、float const&)'のようなシグネチャを禁止しています。 – Barry

-1

std::functionの代わりに関数ポインタにロールバックすることができます。とたとえば:代わりにAction

template<typename ...TArgs> 
using actionPtr = void (*)(TArgs...); 

void DoActionStuff(actionPtr<int, float> callback) 
{ 
    callback(1, 2.0); 
} 

、我々は得るでしょう:

DoActionStuff([](int x, float y) { 
    cout << x << " " << y << endl; 
}); // OK 
DoActionStuff([](int x, float y) { 
    cout << x << " " << y << endl; return x * y; 
}); // Woohoo! Now we get error here! 

そしてFuncのための類推宣言:

template<typename TRet, typename ...TArgs> 
using funcPtr = TRet (*)(TArgs...); 

あなたの(3)に対する主張は多少議論の余地があるので、それは(1)、(2)、(2.5)の要件を満たしています。現代のC++でstd::functionはといくつかの他の機能的なものにspecial featuresといくつかを可能にするので、関数ポインタよりmore idiomaticと思われる。また、can convert(暗黙的に、必要ならば)のように、より一般的ですが、std::functionへの関数ポインタですが、その逆もありません。

+0

@Downvoter、私の答えに何が間違っているか説明してください。 – Sergey

+0

はいstd :: functionにはいくつかの優れた利点があります(クラス内のメンバー関数とのより簡単なinterop)。パフォーマンスに関する私の質問は、std :: functionが 'big'です(私の実装ではおそらく小さな関数を処理するためにスタック上に64バイト)。結局、std :: functionに固執することになるかもしれません...関数シグネチャを完全にガムしているので、一般的にポインタから離れようとしていますが、私が深く進むにつれてこれを念頭に置いておきます... –

0

あなたはSFINAEを使用することがあります。

template <typename F> 
auto DoActionStuff(F&& f) -> decltype(std::declval<int&>() = f(42, 4.2f), void()); 
1

std::functionは合理的に効率的です。これは呼び出し時の2つのポインタの間引きであり、最も高品質なC++標準ライブラリの実装では、渡されるオブジェクトが大きい場合にのみ割り当てます。 (たとえば、MSVC2015は2つの最大値(たとえば、std::string)の場合は割り当てられません。

ピクセルごとのフレームごとのレンダリングループ、または同様の非常に時間がかかる状況で呼び出す場合は、私はstd::functionに恥ずかしがります。スキャンラインごとでさえ、それは受け入れられるでしょう。

したがってstd::function解決#1と#3。

#2については、それは疑わしいです。ほとんどの暗黙的な変換は、あなたがしたいことです。あなたの可能なケースは、戻り値を無視したくないということです。

std::function<void()>何かを返す関数が渡されたときに不平を言うが、戻り値を処理して破棄するために標準で明示的かつ意図的に変更されています。奇妙なことに、int()の署名はvoid()にpasingすることができ、すべてが受け入れられます。

あなたはそれを望んでおらず、それは合理的な立場です。

私が本当にそれを必要としたら、私はstd::functionをラップします。

template<class Sig> 
struct func:std::function<Sig>{ 
    using std::function<Sig>::function; 
}; 
template<class...Args> 
struct func<void(Args...)>:std::function<void(Args...)> { 
    using Base=std::function<void(Args...)>; 

    func()=default; 
    func(func const&)=default; 
    func(func &&)=default; 
    func& operator=(func const&)=default; 
    func& operator=(func &&)=default; 

    template<class Fin, 
    std::enable_if_t<!std::is_same<std::decay_t<Fin>, func>{}, int> =0, 
    std::enable_if_t<std::is_same<std::result_of_t<Fin&(Args...)>, void>{}, int> =0 
    > 
    func(Fin&& fin):Base(std::forward<Fin>(fin)) {} 

    template<class...Fs, 
    std::enable_if_t<(std::sizeof...(Fs)>1), int> =0 
    > 
    func(Fs&&... fs):Base(std::forward<Fs>(fs)...) {} 

    template<class Fin, 
    std::enable_if_t<!std::is_same<std::decay_t<Fin>, func>{}, int> =0, 
    std::enable_if_t<std::is_same<std::result_of_t<Fin&(Args...)>, void>{}, int> =0 
    > 
    func& operator=(Fin&& fin) { 
    Base::operator=(std::forward<Fin>(fin)); 
    return *this; 
    } 
}; 

しかし、私は偽の戻り値が無視できることを受け入れるだけです。

+0

確かに、変換が望まれます。戻り値を無視することはできません。さらにperfの場合、std ::関数への呼び出しは明らかにインライン展開できませんが、まっすぐなテンプレート解法は非常によくあることがあります。それが見える毎ターンにトレードオフがあります。 –

+0

@RogerBほとんど決してinlinable。 gccとclangは場合によっては 'std :: function'をインライン化します。制約のあるテンプレートを使用できます。繰り返しになりますが、リターン値を破棄したくないという欲求は、標準的なC++のイディオムに違反します。ここでは、リターン値を冗長にし、 'void'コンテキストで評価することを期待しています。 – Yakk

関連する問題