ついに(!)
variadic templates
を試してみたかったの
variadic templates
まだこれがない10番目のスタジオに縛られています。
可変長テンプレートを使用するのがどこで役に立たないかについて長い間考えないために、
Typelistがどの
ように見えるかを試してみるというアイデアが生まれました。 それが何であるかまだわからない人のために、私は途中で説明しようとします、そして退屈している人のために-すぐにスクロールできます
Typelist
を使用して一種の三目
Typelist
を
Typelist
ます。
だから
TypeList
:
タイプリスト namespace internal { struct Void { }; }
典型的な
TypeList
は「Head」と「Tail」であり、これらもタイプのリストです。 使用法:
typedef TypeList<float, double, long double> floating_point_types;
以前は、C ++ 11がないと、次のようになりました。
古いタイプリスト template <class H, class T> struct typelist { typedef H head; typedef T tail; }; typedef typelist<float, typelist<double, long double> > floating_point_types;
そして役立つマクロ:
#define TYPELIST_1(T1) typelist<T1, null_typelist> #define TYPELIST_2(T1, T2) typelist<T1, TYPELIST_1(T2) > #define TYPELIST_3(T1, T2, T3) typelist<T1, TYPELIST_2(T2, T3) > ... #define TYPELIST_50...
しかし今では、
variadic templates
おかげで、マクロとリスト内の型の数の制限を取り除くことができます。
実際、興味深いのは、型のリストを操作する方法、その操作を定義する方法、および最終的にそれが与えるものです(より詳細な説明に興味があり、
モダンC ++デザインを見たことがない人-読むことをお勧めします-これが2001年であることは重要ではありません! )
ご覧のとおり、補助型
internal::Void
を定義しました。これはシグナルフラグとして機能し、型のリストは空であると言います(少なくともユーザーが何も指定しなかった場合は
TypeList<>
、またはリストからすべてのアイテムが削除されました)。 最初から始めましょう:
Isempty
Isempty template<typename TL> struct IsEmpty : std::true_type { }; template<> struct IsEmpty<TypeList<internal::Void, internal::Void>> : std::true_type { }; template<typename ...Args> struct IsEmpty<TypeList<Args...>> : std::integral_constant<bool, std::is_same<typename TypeList<Args...>::Head, internal::Void>::value && IsEmpty<typename TypeList<Args...>::Tail>::value> { };
ここでは、他の操作を定義するために必要なほとんどすべてを見ることができます。 ご覧のとおり、まず「バックボーン」を定義します
IsEmpty
型は1つの型によってパラメーター化されます。 本質的に、これは1つの引数を取る「関数」です。 タイプ
TL
は「任意のタイプ」を意味するため、空のリストを持つケースのテンプレートの完全な特殊化を行います:
TypeList<internal::Void, internal::Void>
(
TypeList<>
のみ、またはそのためだけにI定義されたタイプ
EmptyTypeList
)および機能する部分的な特殊化-「任意のタイプリスト用」。 したがって、「関数」はタイプのリストに対してのみ定義されます。
struct IsEmpty : std::true_type
ような便利なものが新しい標準に登場しました。これにより、生活が大幅に簡素化されます:
struct IsEmpty : std::true_type
場合、
IsEmpty
は
value
クラスのメンバー、多数の
typedef
および
bool
への変換演算子があります。
使い方は?:
typedef TypeList<int> TL1; std::cout << std::boolalpha << IsEmpty<TL1>::value << " " << IsEmpty<EmptyTypeList>() << std::endl;
空のリストがあるかどうかは、次の式を定義します。
std::is_same<typename TypeList<Args...>::Head, internal::Void>::value && IsEmpty<typename TypeList<Args...>::Tail>::value
文字通り-「リストの先頭が
void
を示す補助型であり、末尾も空のリストである場合、リストは空です。」 ご覧のとおり、ここでは再帰が使用され、空のリストのテンプレートの完全な特殊化が停止されます。
次:
コンテナ
コンテナ template<typename T, typename TL> struct Contains : std::false_type { }; template<typename ...Args> struct Contains<internal::Void, Args...> : std::false_type { }; template<typename T, typename ...Args> struct Contains<T, TypeList<Args...>> : std::integral_constant<bool, std::is_same<typename TypeList<Args...>::Head, T>::value || Contains<T, typename TypeList<Args...>::Tail>::value > { };
指定したタイプ
T
TL
タイプのリスト内にあるかどうかが
Contains
。 使用法:
使用法:
typedef TypeList<double, float, float, double, int, char, char, int, char> TL; std::cout << std::boolalpha << Contains<char, TL>::value << " " << Contains<float, TypeList<double>>() << std::endl;
再び:「リストの先頭がタイプ
T
である場合、
T
はリスト内にあります。それ以外の場合、
T
リストの末尾にあるかどうかを確認します。」
部分的な専門化-予防措置-誰かがタイプ
internal::Void
使用するとどうなりますか?
長さ
長さ template<typename TL> struct Length : std::integral_constant<unsigned int, 0> { }; template<typename ...Args> struct Length<TypeList<Args...>> : std::integral_constant<unsigned int, IsEmpty<TypeList<Args...>>::value ? 0 : 1 + Length<typename TypeList<Args...>::Tail>::value> { };
リストが空の場合、長さはゼロです。それ以外の場合は1です(「ヘッド」があるため)+テールの長さ:
typedef TypeList<double, float, float, double, int, char, char, int, char> TL; std::cout << Length<TL>::value << " " << Length<EmptyTypeList>() << std::endl;
入力する
template<unsigned int N, typename TL> struct TypeAt { typedef internal::Void type; };
-配列のように、インデックスでタイプを返します。 実装は最初の呼び出しです(タイプ
N
を
int
変更します):
-すべて正常に動作しますが、! -インデックスが大きすぎる場合、警告が表示されます。 現在の実装で抜け出すことは可能ですが、ここでは、
N=-1
場合にテンプレートを正しくインスタンス化する必要があることを考慮する必要があります。 したがって、我々は他の方法で行きます:
template<typename ...Args> struct TypeAt<0, TypeList<Args...>> { typedef typename TypeList<Args...>::Head type; }; template<unsigned int N, typename ...Args> struct TypeAt<N, TypeList<Args...>> { static_assert(N < Length<TypeList<Args...>>::value, "N is too big"); typedef typename TypeAt<N - 1, typename TypeList<Args...>::Tail>::type type; };
-頭のインデックスはゼロであり、他の場合-インデックスを1つ減らし、尾の一部を「食べる」(左から右に移動します)-インデックスをゼロにし、現在の頭が必要なタイプです! 使用法:
typedef TypeList<char, short> TL2; static_assert(std::is_same<TypeAt<1, TL2>::type, short>::value, "Something wrong!");
リスト出力
これらの関数は、通常のタイプリストとネストされたリストを正確に表示するのに役立ちます。たとえば、
typedef TypeList<double, float, float, double, int, char, char, int, char> TL; std::cout << TL() << std::endl; typedef TypeList<TL2, double, TL2> TL10; std::cout << TL10() << std::endl;
{double float float double int char char int char}
{{char short} double {char short}}
追加して追加
追加、追加リストの末尾に追加する関数。わずかな違いがあります。
template<typename TOrTL2, typename TL> struct Append { }; template<typename T, typename ...Args> struct Append<T, TypeList<Args...>> { typedef TypeList<Args..., T> type; }; template<typename ...Args1, typename ...Args2> struct Append<TypeList<Args1...>, TypeList<Args2...>> { typedef TypeList<Args2..., Args1...> type; }; template<typename T, typename TL> struct Add { }; template<typename T, typename ...Args> struct Add<T, TypeList<Args...>> { typedef TypeList<Args..., T> type; };
最初の引数に型のリストを指定して
Append
を使用すると、コンポーネントへの「分解」があります。 すなわち:
typedef TypeList<int> TL1; typedef TypeList<char, short> TL2; std::cout << TL1() << ", " << TL2() << std::endl; std::cout << Add<TL2, TL1>::type() << ", " << Append<TL2, TL1>::type() << std::endl;
{int}, {char short}
{int {char short}}, {int char short}
最初の場合、結果の長さは2で、2番目の場合は3になります。これは、追加されたタイプのリストがコンポーネントに「分解」されるためです。
すべて削除
アイテムを削除する template<typename TOrTL2, typename TL> struct RemoveAll { }; template<typename T, typename ...Args> struct RemoveAll<T, TypeList<Args...>> { private: typedef typename RemoveAll<T, typename TypeList<Args...>::Tail>::type Removed; typedef typename TypeList<Args...>::Head Head; public: typedef typename std::conditional< std::is_same<Head, T>::value, Removed, typename Append<Removed, TypeList<Head>>::type >::type type; }; template<typename T, typename Head> struct RemoveAll<T, TypeList<Head>> { typedef typename std::conditional< std::is_same<Head, T>::value, EmptyTypeList, TypeList<Head>>::type type; }; template<typename T> struct RemoveAll<T, EmptyTypeList> { typedef EmptyTypeList type; };
削除は次のように行われます。
- 空のリストから何も削除できません
- 1つの要素(ヘッドのみ)を持つリストがある場合、ヘッドのタイプが指定されたものと一致する場合は空のリストを返し、そうでない場合は何も変更しません
- 他のすべての場合-テールから要素を削除し、ヘッドタイプが指定したタイプと一致しない場合-削除結果の前に追加します
重要なことは、結果を末尾から削除するとき、結果を別のタイプのリストにグループ化し、結合するときに、グループ化されたタイプのリストを「巻き戻す」
Append
を使用することです。
使用法:
typedef TypeList<double, float, float, double, int, char, char, int, char> TL; std::cout << TL() << std::endl; std::cout << RemoveAll<char, TL>::type() << std::endl;
{double float float double int char char int char}
{double float float double int int}
RemoveAll
別のバージョンを記述して、2番目のタイプのリストから、最初のタイプにあるすべてのタイプを削除できます。 しかし! この場合、他のリストを含むリストには使用できません。
例:
typedef TypeList<double, float, float, double, int, char, char, int, char> TL; typedef TypeList<char, double> TL2; std::cout << TL() << std::endl; std::cout << RemoveAll<TL2, TL>::type() << std::endl;
{double float float double int char char int char}
{float float int int}
RemoveDuplicates
RemoveDuplicates template<typename TL> struct RemoveDuplicates { }; template<> struct RemoveDuplicates<EmptyTypeList> { typedef EmptyTypeList type; }; template<typename ...Args> struct RemoveDuplicates<TypeList<Args...>> { private: typedef TypeList<Args...> TL; typedef typename RemoveAll<typename TL::Head, typename TL::Tail>::type HeadRemovedFromTail; typedef typename RemoveDuplicates<HeadRemovedFromTail>::type TailWithoutDuplicates; public: typedef typename Append<TailWithoutDuplicates, TypeList<typename TL::Head>>::type type; };
重複を削除する関数:
- 空のリストから何も削除できません
- 尾から頭と同じ要素を削除します
- 末尾の関数を再帰的に呼び出す
- 結果と頭を組み合わせます
例:
typedef TypeList<double, float, float, double, int, char, char, int, char> TL; std::cout << TL() << std::endl; std::cout << RemoveDuplicates<TL>::type() << std::endl;
{double float float double int char char int char}
{double float int char}
見つける
リスト内の位置を入力 struct Constants { typedef std::integral_constant<unsigned int, UINT_MAX> npos; }; namespace internal { template<typename T, unsigned int IndexFrom, typename TL> struct FindHelper : std::integral_constant<unsigned int, 0> { }; template<typename T, unsigned int IndexFrom> struct FindHelper<T, IndexFrom, EmptyTypeList> : std::integral_constant<unsigned int, 0> { }; template<typename T, unsigned int IndexFrom, typename ...Args> struct FindHelper<T, IndexFrom, TypeList<Args...>> : std::integral_constant<unsigned int, std::is_same<typename TypeList<Args...>::Head, T>::value ? IndexFrom : IndexFrom + 1 + FindHelper<T, IndexFrom, typename TypeList<Args...>::Tail>::value> { }; }
いくつかのこと:
-
Constants
-定数用。 私たちの場合、要素が見つからなかったことを示す定数に対してのみ(constexpは私のスタジオではサポートされていないため、
UINT_MAX
)
-
internal::FindHelper
実際、リスト内の型を正確に検索する「もの」(!)この型は(追加の
IndexFrom
パラメーター-参照の初期値であり、必要なものではありません:)-必要な場合のために設計されています検索を開始する位置を尋ねます)
再び-複雑なことはありません-指定された型とリストの先頭の型が同じ場合-インデックスはゼロです。そうでない場合は、1tsuだけ右に移動し、リストの末尾に対して同じことを行います。
例:
typedef TypeList<double, float, float, double, int, char, char, int, char> TL; std::cout << std::boolalpha << std::is_same<TypeAt<Find<double, TL>::value, TL>::type, double>() << std::endl;
スライス
スライス namespace internal { template<unsigned int IndexBegin, unsigned int IndexEnd, typename TL> struct SliceHelper { }; template<unsigned int IndexBegin, unsigned int IndexEnd> struct SliceHelper<IndexBegin, IndexEnd, EmptyTypeList> { typedef EmptyTypeList type; }; template<unsigned int IndexBegin, typename ...Args> struct SliceHelper<IndexBegin, IndexBegin, TypeList<Args...>> { typedef TypeList<typename TypeAt<IndexBegin, TypeList<Args...>>::type> type; }; template<unsigned int IndexBegin, unsigned int IndexEnd, typename ...Args> struct SliceHelper<IndexBegin, IndexEnd, TypeList<Args...>> { private: static_assert(IndexEnd >= IndexBegin, "Invalid range"); typedef TypeList<Args...> TL; public: typedef typename Add< typename TypeAt<IndexEnd, TL>::type, typename SliceHelper<IndexBegin, IndexEnd - 1, TL>::type >::type type; }; }
リストの指定された部分を「切り取る」:
- 空のリストからは何も取得できません
- 指定された開始(
IndexBegin
)と終了( IndexEnd
)が一致する場合、これはTypeAt
操作に似ています
指定された範囲の終わりから開始して、要素を取得し、再帰呼び出しの結果に追加します(指定された範囲の終わりが1cu減少します)。
ご清聴ありがとうございました!