web-dev-qa-db-ja.com

C ++ 11可変個引数std :: functionパラメーター

testという名前の関数は、パラメーターとしてstd :: function <>を取ります。

_template<typename R, typename ...A>
void test(std::function<R(A...)> f)
{
    // ...
}
_

しかし、私が次のことをすると:

_void foo(int n) { /* ... */ }

// ...

test(foo);
_

コンパイラ(gcc 4.6.1)はno matching function for call to test(void (&)(int))と言います。

最後の行をtest(foo)でコンパイルして正しく機能させるには、どうすればtest()関数を変更できますか? test()関数では、std :: function <>のタイプのfが必要です。

つまり、コンパイラに関数の署名(例ではfoo)を決定させ、それを自動的にstd::function<void(int)>に変換させるテンプレートトリックはありますか?

編集

これをラムダ(ステートレスとステートレスの両方)でも機能させたいと思います。

19
Daniel K.

オーバーロードを使用したいようです

_template<typename R, typename ...A>
void test(R f(A...))
{
    test(std::function<R(A...)>(f));
}
_

この単純な実装は、渡そうとするすべてではないにしてもほとんどの関数を受け入れます。エキゾチックな関数は拒否されます(void(int...)など)。より多くの作業を行うと、より一般的になります。

std::functionは、呼び出し可能インターフェースを実装します。つまり、関数のように見えますが、呼び出し可能オブジェクトをstd::functionsにする必要があるという意味ではありません。

template< typename F > // accept any type
void test(F const &f) {
    typedef std::result_of< F( args ) >::type R; // inspect with traits queries
}

ダックタイピングは、テンプレートメタプログラミングの最良のポリシーです。テンプレート引数を受け入れるときは、具体的ではなく、クライアントにインターフェイスを実装させるだけです。

本当にたとえば、変数またはそのようなクレイジーなものを再ターゲットするためにstd::functionが必要で、入力が生の関数ポインターであることがわかっている場合は、生の関数ポインタータイプを分解できます。そしてそれをstd::functionに再構成します。

template< typename R, typename ... A >
void test( R (*f)( A ... ) ) {
    std::function< R( A ... ) > internal( f );
}

関数内にカプセル化されているため、ユーザーはstd::functionを渡すことができなくなりました。既存のコードを別のオーバーロードとして保持し、それに委任することもできますが、インターフェイスを単純に保つように注意してください。

ステートフルラムダについては、その場合の処理​​方法がわかりません。それらは関数ポインタに分解されず、私が知る限り、引数の型を照会したり推測したりすることはできません。この情報は、良くも悪くも、std::functionをインスタンス化するために必要です。

7
Potatoswatter

「バイナリ区切り」(ダイナミックライブラリ、「不透明」APIなど)でない限り、値でstd::functionを受け入れることは、通常はお勧めできません。これは、目撃したばかりのため、オーバーロードで大混乱を引き起こすためです。関数が実際に値でstd::functionを取る場合、オーバーロードの問題を回避するためにオブジェクトを構築するのは呼び出し元の負担になることがよくあります(関数がオーバーロードされている場合)。

ただし、テンプレートを作成したので、型消去の利点のためにstd::function(パラメーター型として)を使用していない可能性があります。あなたがしたいことが任意のファンクターを検査することであるなら、あなたはそのためのいくつかの特性を必要とします。例えば。 Boost.FunctionTypesには、result_typeparameter_typesなどの特性があります。最小限の機能的な例:

#include <functional>

#include <boost/function_types/result_type.hpp>
#include <boost/function_types/parameter_types.hpp>
#include <boost/function_types/function_type.hpp>

template<typename Functor>
void test(Functor functor) // accept arbitrary functor!
{
    namespace ft = boost::function_types;

    typedef typename ft::result_type<Functor>::type result_type;
    typedef ft::parameter_types<Functor> parameter_types;
    typedef typename boost::mpl::Push_front<
        parameter_types
        , result_type
    >::type sequence_type;
    // sequence_type is now a Boost.MPL sequence in the style of
    // mpl::vector<int, double, long> if the signature of the
    // analyzed functor were int(double, long)

    // We now build a function type out of the MPL sequence
    typedef typename ft::function_type<sequence_type>::type function_type;

    std::function<function_type> function = std::move(functor);
}

最後に、一般的なケースでは、ファンクターをイントロスペクトすること(つまり、結果タイプと引数タイプをプロディングすること)はお勧めしません。これは、ポリモーフィックファンクターでは機能しないためです。いくつかのオーバーロードされたoperator()を考えてみましょう。その場合、「正規の」結果タイプまたは引数タイプはありません。 C++ 11では、あらゆる種類のファンクターを「熱心に」受け入れるか、必要に応じてSFINAEやstatic_assertなどの手法を使用して制約し、後で(パラメーターが使用可能な場合)std::result_ofを使用して結果タイプを検査することをお勧めします- 与えられた引数のセットに対して。前もって制約することが望ましい場合は、目的がファンクターを例えばに格納することである場合です。 std::function<Sig>のコンテナ。

前の段落の意味を理解するには、上記のスニペットを多態的なファンクターでテストするだけで十分です。

4
Luc Danton

これは古いもので、同じトピックについてはあまり見つけられないようですので、先に進んでメモをとろうと思いました。

GCC 4.8.2でコンパイルすると、次のように機能します。

template<typename R, typename... A>
R test(const std::function<R(A...)>& func)
{
    // ...
}

ただし、ポインターやラムダなどを渡して呼び出すことはできません。ただし、次の2つの例はどちらも使用できます。

test(std::function<void(int, float, std::string)>(
        [](int i, float f, std::string s)
        {
            std::cout << i << " " << f << " " << s << std::endl;
        }));

また:

void test2(int i, float f, std::string s)
{
    std::cout << i << " " << f << " " << s << std::endl;
}

// In a function somewhere:
test(std::function<void(int, float, std::string)>(&test2));

これらの欠点は明らかに目立つはずです。std:: functionを明示的に宣言する必要がありますが、これは少し見苦しいかもしれません。

とはいえ、着信関数を呼び出すために展開されるタプルと一緒にそれを投げました。それは機能し、テスト関数を呼び出すために何をしているのかをもう少し明示的に言う必要があります。

タプルのものを含むサンプルコード、それで遊びたい場合: http://ideone.com/33mqZA

3
user3694249