2

最も一般的な用語では、私の問題は次のとおりです。コンパイル時に、実行時に任意の順序で反復および呼び出す必要がある異種関数ポインターのシーケンス(潜在的に異なるアリティを持つ)を定義します。

自分自身をC++に制約しますが、最も適切なコンテナー、反復、および呼び出しメカニズムは何でしょうか?

この問題は、タプルを含まないより単純な解決策を後で見つけた現実世界の状況によって動機付けられましたが、それはより自然に特化しています。

もともと私はこのようなことをしようとしました:

//type variables Y... have to be convertible to parameters of every function from the tuple std::tuple<T...> in order for this to compile
template<size_t n, typename... T, typename... Y>
void callFunNth(std::tuple<T...> &tpl, size_t i, Y... args) {
    if (i == n)
        std::get<n>(tpl)(args...); 
    else
        callFunNth<(n < sizeof...(T)-1? n+1 : 0)>(tpl, i, args...);
}
template<typename... T, typename... Y>
void  callFun(std::tuple<T...> &tpl, size_t i, Y... args) {
    callFunNth<0>(tpl,i, args...);
}

int main() 
{
    using T1 = int;
    namespace mpi = boost::mpi;
    //Several instantiations of boost::mpi::reduce algorithm I am interested in
    auto algs = make_tuple(boost::bind((void (*)(const mpi::communicator&, const T1*, T1, T1*, std::plus<T1>, int))mpi::reduce<T1, std::plus<T1>>, _1, _2, _3, _4, std::plus<T1>(), _5),
                           boost::bind((void (*)(const mpi::communicator&, const T1*, T1, T1*, mpi::minimum<T1>, int))mpi::reduce<T1, mpi::minimum<T1>>, _1, _2, _3, _4, mpi::minimum<T1>(), _5),
                           boost::bind((void (*)(const mpi::communicator&, const T1*, T1, T1*, std::minus<T1>, int))mpi::reduce<T1, std::minus<T1>>, _1, _2, _3, _4, std::minus<T1>(), _5)
                          );

    //Iterate through the tuple and call each algorithm
    for(size_t i=0; i < std::tuple_size<decltype(algs)>::value;i++)
        callFun(algs, i, /*actual arguments to each algorithm*/);
}

このアプローチの問題は、callFunNthがすべての提供された引数をコンパイルするには、提供されたタプル内のすべての関数のパラメーターに型変換可能である必要があることです。これにより、前述の関数の異質性が大幅に制限され、std::bindの使用が強制されます。またはboost::bindでこれを回避します。

タイプが相互に変換可能である場合、次のように書くことができます。

template <typename T, typename U>
void fja(T x, U y) { 
    std::cout << x << std::endl;
}
auto funs = std::make_tuple(fja<int,std::string>, fja<double,std::string>, fja<char,std::string>);
callFun(funs, 2, 'a', "Char");
callFun(funs, 1, 2.45, "Decimal");
callFun(funs, 0, 1, "Integer");

'a'、 '2.45'、および'1'をそれぞれstdoutに送信します

4

4 に答える 4

2

関数オブジェクトは として保存する必要がありますstd::vector<std::function<const boost::mpi::communicator&, const T1*, int, T1*, int>>。管理がはるかに簡単です。

関数のタプルを使用する必要がある場合は、以下を参照してください。


C++ 標準ライブラリには、コンパイル時のiota.

同じ引数を使用してすべての関数を呼び出す必要がある場合の代替方法を次に示します。まず、可変長整数リストを作成します( https://github.com/kennytm/utils/blob/master/vtmp.hppintegers<0, 1, 2, ..., n-1>からコピー):

template <size_t... ns>
struct integers
{
    template <size_t n>
    using push_back = integers<ns..., n>;

};

namespace xx_impl
{
    template <size_t n>
    struct iota_impl
    {
        typedef typename iota_impl<n-1>::type::template push_back<n-1> type;
    };

    template <>
    struct iota_impl<0>
    {
        typedef integers<> type;
    };
}

template <size_t n>
using iota = typename xx_impl::iota_impl<n>::type;

次に、unpack 操作を直接使用します。

template <typename... T, size_t... ns, typename... Y>
void call_all_impl(const std::tuple<T...>& funcs,
                   const integers<ns...>&,
                   Y... args) {
    __attribute__((unused))
    auto f = {(std::get<ns>(funcs)(args...), 0)...};
}

template <typename T, typename... Y>
void call_all(const T& funcs, Y&&... args) {
    call_all_impl(funcs,
                  iota<std::tuple_size<T>::value>(), 
                  std::forward<Y>(args)...);
}

例えば、

int main() {
    call_all(std::make_tuple([](int x, double y){ printf("1: %d %g\n", x, y); },
                             [](double x, int y){ printf("2: %e/%d\n", x, y); },
                             [](int x, int y){ printf("3: %#x %#x\n", x, y); }),
            4, 9);
}

版画

1:4 9
2: 4.000000e+00/9
3: 0x4 0x9

わずかな変更によりi、実行時に選択された -th 引数だけを呼び出すようにすることができます。

template <typename... T, size_t... ns, typename... Y>
void call_one_impl(const std::tuple<T...>& funcs, size_t which,
                   const integers<ns...>&,
                   Y... args) {
    __attribute__((unused))
    auto f = {(ns == which && (std::get<ns>(funcs)(args...), 0))...};
}

template <typename T, typename... Y>
void call_one(const T& funcs, size_t which, Y&&... args) {
    call_one_impl(funcs, which,
                  iota<std::tuple_size<T>::value>(),
                  std::forward<Y>(args)...);
}

例えば、

int main() {
    auto t = std::make_tuple([](int x, double y){ printf("1: %d %g\n", x, y); },
                             [](double x, int y){ printf("2: %e/%d\n", x, y); },
                             [](int x, int y){ printf("3: %#x %#x\n", x, y); });

    call_one(t, 2, 6.5, 7.5);
    call_one(t, 0, 4, 9);
    call_one(t, 1, 5.8, 8);
}

版画

3: 0x6 0x7
1:4 9
2: 5.800000e+00/8
于 2012-07-16T14:54:06.850 に答える
1

異種コンテナーの主なライブラリはBoost.Fusionです。その Web サイトでわかるように、このようなタスクにはコンパイル時のポリモーフィック ファンクターが使用されます。

于 2012-07-16T14:38:46.103 に答える
0

関数を格納するためにタプルを使用するのは悪い考えだと思います
私にとっては、関数ポインタを作成する方が簡単です

typedef impl_defined pointer_to_function

機能をにプッシュします

std::vector<pointer_to_function>
于 2012-07-16T15:03:03.393 に答える
0

この問題をもう少しいじった後、型共用体を使用することは、それに対処するための合理的な良い方法の 1 つかもしれないと信じています。 .

とにかく、これは C++ で型共用体を使用して問題にアプローチする方法です。

int main()
{
    auto f1 = [](int x) { std::cout << x << std::endl;};
    auto f2 = [](int x, std::string y) { std::cout << y << std::endl;};
    auto f3 = [](const std::vector<int> &x) { std::copy(x.begin(),x.end(),std::ostream_iterator<int>(std::cout," "));};
    using tFun1 = decltype(f1);
    using tFun2 = decltype(f2);
    using tFun3 = decltype(f3);
    using funUnion = boost::variant<tFun1,tFun2,tFun3>;

    std::vector<funUnion> funs = {f1,f2,f3};
    for(auto &ff : funs) {
        if (tFun1* result = boost::get<tFun1>(&ff)) (*result)(2);
        else
        if (tFun2* result = boost::get<tFun2>(&ff)) (*result)(2,"Hello!");
        else
        if (tFun3* result = boost::get<tFun3>(&ff)) (*result)({1,2,3,4,5});
    }
}
于 2012-07-17T16:20:55.930 に答える