40

このように、可変引数テンプレート引数を持つテンプレート関数があります

template<typename Args...>
void ascendingPrint(Args... args) { /* ... */ }

そして書きたい

template<typename Args...>
void descendingPrint(Args... args) {
  /* implementation using ascendingPrint()? */
}

パラメータパックを渡す前に順序を逆にする args方法、つまり疑似コードで:

template<typename Args...>
void descendingPrint(Args... args) {
  ascendingPrint( reverse(args) );
}
4

6 に答える 6

30

全体的なアプローチと使用法


全体的なアプローチは、の完全な転送機構を利用して、引数std::tuple参照の に詰め込むことにあります。std::forward_as_tuple()

これは、実行時に発生するオーバーヘッドが非常に小さく、不要なコピー/移動操作が発生しないことを意味します。また、フレームワークは再帰を使用しないため (インデックスを生成するために避けられないコンパイル時の再帰を除いて)、コンパイラが再帰関数呼び出しをインライン化できなかった場合でも実行時のオーバーヘッドのリスクはありません (これはほとんどありません)。とにかく、これはより学術的な議論です)。

さらに、このソリューションは一般的であり、ヘッダーのみのライブラリとして使用して、引数を逆にして最小限の労力で関数を呼び出すことができdescending_print()ます。ascending_print()

これは次のようになります。

MAKE_REVERT_CALLABLE(ascending_print)

template<typename... Args>
void descending_print(Args&&... args)
{
    revert_call(REVERT_ADAPTER(ascending_print), std::forward<Args>(args)...);
} 

以下は、実装のプレゼンテーションです。


最初のステップ: 型シーケンスを元に戻す


型シーケンスを元に戻す簡単な方法を次に示します。

#include <tuple>
#include <type_traits>

template<typename, typename>
struct append_to_type_seq { };

template<typename T, typename... Ts>
struct append_to_type_seq<T, std::tuple<Ts...>>
{
    using type = std::tuple<Ts..., T>;
};

template<typename... Ts>
struct revert_type_seq
{
    using type = std::tuple<>;
};

template<typename T, typename... Ts>
struct revert_type_seq<T, Ts...>
{
    using type = typename append_to_type_seq<
        T,
        typename revert_type_seq<Ts...>::type
        >::type;
};

小さなテスト プログラム:

int main()
{
    static_assert(
        std::is_same<
            revert_type_seq<char, int, bool>::type,
            std::tuple<bool, int, char>
            >::value,
        "Error"
        );
}

そしてライブの例


2 番目のステップ: タプルを元に戻す


次のステップは、タプルを元に戻すことです。通常のインデックストリック機械を考えると:

template <int... Is>
struct index_list { };

namespace detail
{
    template <int MIN, int N, int... Is>
    struct range_builder;

    template <int MIN, int... Is>
    struct range_builder<MIN, MIN, Is...>
    {
        typedef index_list<Is...> type;
    };

    template <int MIN, int N, int... Is>
    struct range_builder : public range_builder<MIN, N - 1, N - 1, Is...>
    { };
}

template<int MIN, int MAX>
using index_range = typename detail::range_builder<MIN, MAX>::type;

上で定義した関数と合わせて、タプルは次のように簡単に元に戻すことができます。

template<typename... Args, int... Is>
typename revert_type_seq<Args...>::type
revert_tuple(std::tuple<Args...> t, index_list<Is...>)
{
    using reverted_tuple = typename revert_type_seq<Args...>::type;

    // Forwarding machinery that handles both lvalues and rvalues...
    auto rt = std::forward_as_tuple(
            std::forward<
                typename std::conditional<
                    std::is_lvalue_reference<
                        typename std::tuple_element<Is, reverted_tuple>::type
                        >::value,
                    typename std::tuple_element<Is, reverted_tuple>::type,
                    typename std::remove_reference<
                        typename std::tuple_element<Is, reverted_tuple>::type
                        >::type
                    >::type
                >(std::get<sizeof...(Args) - Is - 1>(t))...
        );

    return rt;
}

template<typename... Args>
typename revert_type_seq<Args...>::type
revert_tuple(std::tuple<Args...> t)
{
    return revert_tuple(t, index_range<0, sizeof...(Args)>());
}

簡単なテスト プログラムを次に示します。

#include <iostream>

int main()
{
    std::tuple<int, int, char> t(42, 1729, 'c');
    auto rt = revert_tuple(t);

    std::cout << std::get<0>(rt) << " "; // Prints c
    std::cout << std::get<1>(rt) << " "; // Prints 1729
    std::cout << std::get<2>(rt) << " "; // Prints 42
}

これが実際のです。


3 番目のステップ: 関数の引数を元に戻す


最後のステップは、ターゲット関数を呼び出すときにタプルをアンパックすることです。数行を節約するための別の一般的なユーティリティを次に示します。

template<typename... Args>
typename revert_type_seq<Args...>::type
make_revert(Args&&... args)
{
    auto t = std::forward_as_tuple(std::forward<Args>(args)...);
    return revert_tuple(t);
}

上記の関数は、提供された引数を要素とするタプルを作成しますが、順序は逆です。ターゲットを定義する準備ができていません:

template<typename T>
void ascending_print(T&& t)
{
    std::cout << std::forward<T>(t) << " ";
}

template<typename T, typename... Args>
void ascending_print(T&& t, Args&&... args)
{
    ascending_print(std::forward<T>(t));
    ascending_print(std::forward<Args>(args)...);
}

上記の関数は、提供されたすべての引数を出力します。そして、これがどのように書くことができるかですdescending_print():

template<typename T, int... Is>
void call_ascending_print(T&& t, index_list<Is...>)
{
    ascending_print(std::get<Is>(std::forward<T>(t))...);
}

template<typename... Args>
void descending_print(Args&&... args) {
    call_ascending_print(make_revert(std::forward<Args>(args)...),
         index_range<0, sizeof...(Args)>());
}

再び簡単なテストケース:

int main()
{
    ascending_print(42, 3.14, "Hello, World!");
    std::cout << std::endl;
    descending_print(42, 3.14, "Hello, World!");
}

そしてもちろん、ライブの例です。


最終ステップ: 簡素化


上記の解決策は理解するのが簡単ではないかもしれませんが、簡単に使用でき、非常に柔軟にすることができます。いくつかの一般的な関数が与えられた場合:

template<typename F, typename... Args, int... Is>
void revert_call(F&& f, index_list<Is...>, Args&&... args)
{
    auto rt = make_revert(std::forward<Args>(args)...);
    f(std::get<Is>(rt)...);
}

template<typename F, typename... Args>
void revert_call(F&& f, Args&&... args)
{
    revert_call(f, index_range<0, sizeof...(Args)>(), 
                std::forward<Args>(args)...);
}

そして、いくつかのマクロ定義 (関数テンプレートのオーバーロード セットを作成する方法が見つかりませんでした。申し訳ありません):

#define MAKE_REVERT_CALLABLE(func) \
    struct revert_caller_ ## func \
    { \
        template<typename... Args> void operator () (Args&&... args) \
        { func(std::forward<Args>(args)...); } \
    };

#define REVERT_ADAPTER(func) \
    revert_caller_ ## func()

逆の順序で引数を指定して呼び出されるように任意の関数を適応させることは非常に簡単になります。

MAKE_REVERT_CALLABLE(ascending_print)

template<typename... Args>
void descending_print(Args&&... args)
{
    revert_call(REVERT_ADAPTER(ascending_print), std::forward<Args>(args)...);
}

int main()
{
    ascending_print(42, 3.14, "Hello, World!");
    std::cout << std::endl;
    descending_print(42, 3.14, "Hello, World!");
}

いつものように、ライブの例を締めくくります。

于 2013-04-09T14:31:14.513 に答える
16

コメントで言及した簡単なアプローチは次のとおりです。インデックスを逆に生成し、それを使用してタプルをアンパックします。

// reversed indices...
template<unsigned... Is> struct seq{ using type = seq; };

template<unsigned I, unsigned... Is>
struct rgen_seq : rgen_seq<I-1, Is..., I-1>{};

template<unsigned... Is>
struct rgen_seq<0, Is...> : seq<Is...>{};

#include <tuple>

namespace aux{
template<class Tup, unsigned... Is>
void descending_print(Tup&& t, seq<Is...>)
{
    ascending_print(std::get<Is>(std::forward<Tup>(t))...);
}
} // aux::

template<class... Args>
void descending_print(Args&&... args)
{
    auto t = std::forward_as_tuple(std::forward<Args>(args)...);
    aux::descending_print(t, rgen_seq<sizeof...(Args)>{});
}

実例。

于 2013-04-09T17:22:31.320 に答える
15

以下は、特殊化された の再帰的なrevert<>実装です。

// forward decl
template<class ...Tn>
struct revert;

// recursion anchor
template<>
struct revert<>
{
    template<class ...Un>
    static void apply(Un const&... un)
    {
        ascendingPrint(un...);
    }
};

// recursion
template<class T, class ...Tn>
struct revert<T, Tn...> 
{
    template<class ...Un>
    static void apply(T const& t, Tn const&... tn, Un const&... un)
    {
        // bubble 1st parameter backwards
        revert<Tn...>::apply(tn..., t, un...);
    }
};

// using recursive function
template<class A, class ...An>
void descendingPrint(A const& a, An const&... an)
{
    revert<An...>::apply(an..., a);
}

これはgcc-4.6/7/8およびclangで動作し、おそらく標準に準拠しています。唯一の難しい部分は の呼び出しですrevert<Tn...>::apply(tn..., t, un...)

ただし、再帰によくあるように、ターゲット関数の多くのテンプレートインスタンス化を生成し(コードの肥大化)、完全な転送を使用しないという欠点がありますが、これは問題になる可能性があります(ただし、使用するように改善できる可能性があります) .

于 2013-04-09T16:51:07.893 に答える
4

私のソリューションは完全な転送をサポートし、再帰を伴いません:

#include <iostream>
#include <utility>
#include <tuple>

#include <cstdlib>

template< typename ...types >
void
ascendingPrint(types &&... _values)
{
    (std::cout << ... << std::forward< types >(_values)) << std::endl;
}

template< typename ...types, std::size_t ...indices >
void
descendingPrintHelper(std::tuple< types... > const & refs, std::index_sequence< indices... >)
{
    constexpr std::size_t back_index = sizeof...(indices) - 1;
    return ascendingPrint(std::forward< std::tuple_element_t< back_index - indices, std::tuple< types... > > >(std::get< back_index - indices >(refs))...);
}

template< typename ...types >
void
descendingPrint(types &&... _values)
{
    auto const refs = std::forward_as_tuple(std::forward< types >(_values)...);
    return descendingPrintHelper(refs, std::make_index_sequence< sizeof...(types) >{});
}

int
main()
{
    ascendingPrint(1, ' ', 2, ' ', 3);
    descendingPrint(1, ' ', 2, ' ', 3);
    return EXIT_SUCCESS;
}

実際の例(またはさらに単純化)。

また、最新のコンパイラは、不要なものをすべて完全に最適化できます: https://godbolt.org/g/01Qf6w

于 2015-06-25T08:12:20.730 に答える