2

私はC++とboostでプログラムを書きました。引数の数が不明な関数からファンクターを生成するテンプレートクラスを作成することは可能my_call<func>(vector<variant>)ですbool fun(string)bool fun(int, int, string)

4

1 に答える 1

1

まず、boost::variant<>が保持できるすべての可能な型のリストを必要とするクラス テンプレートであることを認識することが重要です。したがって、 だけではvector<variant>なく、vector<variant<string, double>>、またはvector<variant<int, double, string, my_class>>があり、それらを混在させることはできません。

boost::anyこれにより、ではなくを使用したいと思うようになりましたboost::variant<>。したがって、ここでは、 で動作しboost::variant、使用するようにわずかに変更できるソリューションを提示しboost::anyます。そのため、好みのバージョンを選択できます。


まず、ソリューションの使い方は簡単ですが、理解するのはそれほど簡単ではないことを認めなければなりません。そのため、最初にいくつかの機械を紹介する必要があります。この機構は、バリアント ベースのソリューションとエニーベースのソリューションの両方に共通です。

//=============================================================================
// META-FUNCTIONS FOR CREATING INDEX LISTS

// The structure that encapsulates index lists
template <size_t... Is>
struct index_list
{
};

// Collects internal details for generating index ranges [MIN, MAX)
namespace detail
{
    // Declare primary template for index range builder
    template <size_t MIN, size_t N, size_t... Is>
    struct range_builder;

    // Base step
    template <size_t MIN, size_t... Is>
    struct range_builder<MIN, MIN, Is...>
    {
        typedef index_list<Is...> type;
    };

    // Induction step
    template <size_t MIN, size_t N, size_t... Is>
    struct range_builder : public range_builder<MIN, N - 1, N - 1, Is...>
    {
    };
}

// Meta-function that returns a [MIN, MAX) index range
template<size_t MIN, size_t MAX>
using index_range = typename detail::range_builder<MIN, MAX>::type;

メタクラスを使用すると、コンパイル時の整数のシーケンスをindex_range定義できます。Jonathan Wakelyによって、この種の構造を標準化するという興味深い提案がなされました。これにより、この機構全体が必要なくなります。ただし、当面は、上記のようにこれを手動でコーディングする必要があります。


コンパイル時の整数シーケンスを作成できるようになったので、可変個引数テンプレート引数アンパッキングを利用して、引数のベクトルをvariant通常の引数リストに変換するディスパッチ メカニズムを作成できます。具象variant<>型をテンプレート引数として提供する必要があることに注意してください。これは、 に基づくソリューションには必要ありませんany

// Headers needed for the implementation of the dispatcher
#include <vector>
#include <functional>
#include <boost/variant.hpp>

// Just for convenience
using namespace std;
using boost::variant;

//============================================================================
// DISPATCHER IMPLEMENTATION

// Call dispatching mechanism: notice how the underlying variant type
// must be provided as a template argument (the first one)
template<typename VT, typename R, typename... Args>
struct dispatcher
{

    template<typename F>
    dispatcher(F f) : _f(f) { }

    // The call operator which performs the variant dispatch
    R operator () (vector<VT> const& v)
    {
        if (v.size() != sizeof...(Args))
        {
            // Wrong number of arguments provided!
            return false;
        }

        // Delegates to internal function call: needed for deducing
        // a sequence of integers to be used for unpacking.
        index_range<0, sizeof...(Args)> indexes;
        return do_call(v, indexes);
    }

private:

    // The heart of the dispatching mechanism
    template<size_t... Is>
    R do_call(vector<VT> const& v, index_list<Is...> indexes)
    {
        return _f((get_ith<Args>(v, Is))...);
    }

    // Helper function that extracts a typed value from the variant.
    template<typename T>
    T get_ith(vector<VT> const& v, size_t i)
    {
        return boost::get<T>(v[i]);
    }

    // Wrapper that holds the function to be invoked.
    function<R(Args...)> _f;
};

// Helper function that allows deducing the input function signature
template<typename VT, typename R, typename... Args>
function<R (vector<VT> const&)> get_dispatcher(R (*f)(Args...))
{
    dispatcher<VT, R, Args...> d(f);
    return d;
}

最後に、これをどのように使用できるかを簡単に説明します。以下のような 2 つのテスト関数があるとします。

#include <iostream>

bool test1(string s, double d)
{
    cout << s << " " << d << endl;
    return true;
}

bool test2(int i1, int i2, string s1, string s2)
{
    cout << i1 << " " << i2 << " " << s1 << " " << s2 << endl;
    return true;
}

私たちが望むのは、バリアントのベクトルを構築することによってそれらを呼び出し、それを目的の関数にディスパッチすることです。繰り返しますが、バリアントが保持できるすべての型のリストを指定する必要があるという事実を強調しなければなりません。ここでは、これらの型がstringdouble、およびintであると仮定しますが、プログラムは異なるものでも動作する可能性があります。

std::function<>また、このソリューションは、さまざまな型のファンクターを作成し、それらを均一に呼び出すことを可能にする型消去を実現することに基づいています。したがって、このための便利な型定義std::function<>(使用する型に依存しvariant<>ます) も提供されます。

int main()
{
    // A helper type definition for the variant
    typedef variant<int, double, string> vt;

    // A helper type definition for the function wrapper
    typedef function<bool (vector<vt>)> dispatcher_type;

    // Get a caller for the first function
    dispatcher_type f1 = get_dispatcher<vt>(test1);

    // Prepare arguments for the first function
    vector<vt> v = {"hello", 3.14};

    // Invoke the first function
    f1(v);

    // Get a caller for the second function
    dispatcher_type f2 = get_dispatcher<vt>(test2);

    // Prepare arguments for the second function
    v.assign({1, 42, "hello", "world"});

    // Invoke the second function
    f2(v);
}

すべてのディスパッチャーには typeがあるため、それらをdispatcher_type簡単にコンテナーに入れることができます。ただし、間違った数の引数で関数を呼び出そうとすると、実行時にのみ検出されるという事実に注意する必要があります(コンパイル時に、含まれる要素の数を知ることは不可能std::vector<>です)。したがって、適切な注意を払う必要があります。


約束どおり、このソリューションを少し変更して、boost::anyではなくを使用しますboost::variant。利点は、任意のboost::anyを保持できるため、関数の引数として使用できる型のリストを指定する必要がないことです。

ヘルパー機構は変更されていませんが、コア ディスパッチャー クラス テンプレートは次のように変更する必要があります。

#include <vector>
#include <functional>
#include <boost/any.hpp>

using namespace std;
using boost::any;

//=============================================================================
// DISPATCHER IMPLEMENTATION

template<typename R, typename... Args>
struct dispatcher
{

    template<typename F>
    dispatcher(F f) : _f(f) { }

    // The call operator which performs the dispatch
    R operator () (vector<any> const& v)
    {
        if (v.size() != sizeof...(Args))
        {
            // Wrong number of arguments provided!
            return false;
        }

        // Delegates to internal function call: needed for deducing
        // a sequence of integers to be used for unpacking.
        index_range<0, sizeof...(Args)> indexes;
        return do_call(v, indexes);
    }

private:

    // The heart of the dispatching mechanism
    template<size_t... Is>
    R do_call(vector<any> const& v, index_list<Is...> indexes)
    {
        return _f((get_ith<Args>(v, Is))...);
    }

    // Helper function that extracts a typed value from the variant.
    template<typename T>
    T get_ith(vector<any> const& v, size_t i)
    {
        return boost::any_cast<T>(v[i]);
    }

    // Wrapper that holds the function to be invoked.
    function<R(Args...)> _f;
};

// Helper function
template<typename R, typename... Args>
function<R (vector<any> const&)> get_dispatcher(R (*f)(Args...))
{
    dispatcher<R, Args...> d(f);
    return d;
}

ご覧のとおり、VTテンプレート引数は消えています。get_dispatcher特に、テンプレート引数を明示的に指定せずに呼び出すことができます。ベースのソリューション用に定義したのと同じテスト関数を使用して、ルーチンvariantをどのように適応させるかを次に示します。main()

int main()
{
    // Helper type definition
    typedef function<bool (vector<any>)> dispatcher_type;

    // Get a caller for the first function
    dispatcher_type f1 = get_dispatcher(test1);

    // Get a caller for the second function
    dispatcher_type f2 = get_dispatcher(test2);
    // Prepare arguments for the first function

    vector<any> v = {string("hello"), 3.14};

    // Invoke the first function
    f1(v);

    // Prepare arguments for the second function
    v.assign({1, 42, string("hello"), string("world")});

    // Invoke the second function
    f2(v);
}

唯一の欠点は、文字列リテラルは typeであり、配列を使用して type のオブジェクトを初期化できないboost::anyため、文字列リテラルを明示的に割り当てることができないことです。char []any

any a = "hello"; // ERROR!

したがって、それらをstringオブジェクトにラップするか、明示的にへのポインターに変換する必要がありますchar const*

any a = string("hello"); // OK
any b = (char const*)"hello"; // OK

これが大きな問題ではない場合は、おそらくこの 2 番目の解決策を選択することをお勧めします。

于 2013-02-02T15:55:10.243 に答える