全体的なアプローチと使用法
全体的なアプローチは、の完全な転送機構を利用して、引数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!");
}
いつものように、ライブの例を締めくくります。