Variadic Templates
from the respected FlexFerrum, he decided to practice metaprogramming and write his own implementation of a data structure called Tuple
(Tuple) using templates with a variable number of arguments. For those who are not familiar, a tuple is a data structure that stores data of different types simultaneously. In our particular case, this will be a template class that stores data of the types that were passed to it as template parameters (taking into account the order).
DataHolder
class was DataHolder
, which stores in itself what it was parameterized with:
template<typename T> struct DataHolder { T m_data; }; template<typename Op, typename … F> class Composer : public DataHolder<F> … { // ... };
template <class T, class ... REST> class Tuple : public Tuple<REST ...> { public: Tuple(); Tuple(const T & _p1, const REST & ... _rest); /// returns elements count inline constexpr static unsigned int Count() { return mIndex + 1; } // ... protected: private: T mMember = T(); static constexpr unsigned int mIndex = TupleSuper::Count(); } // template <class T> class Tuple { public: Tuple(); Tuple(const T & _p1); /// returns elements count inline constexpr static unsigned int Count() { return mIndex + 1; } // ... protected: private: T mMember = T(); static constexpr unsigned int mIndex = 0; }
Tuple
types in our inheritance chain as follows: type with index 0 is the lowest descendant, with index 1 is its immediate ancestor, etc. by induction. When transferring too many numbers, we should receive an error at compile time. You can make such a class by defining a terminating specialization for index 0, and a more general definition for index N by specifying with index N is 1. We will define the tuple
itself as using
(or typedef
) in the inside of our “indexer”. Let's call our class plainly TupleIndexer
.
template <class T, class ... REST> class Tuple; template <unsigned int INDEX, class T, class ... REST> class TupleIndexer { public: using TupleIndexerDeeper = TupleIndexer<INDEX - 1, REST ...>; using TupleType = typename TupleIndexerDeeper::TupleType; }; template <class T, class ... REST> class TupleIndexer<0, T, REST ...> { public: using TupleType = Tuple<T, REST ...>; };
Tuple
to Tuple
, calculated by index through static_cast
. To calculate the return value from the Get
method, we will create a synonym for the left-most field in our Tuple
and retrieve it from the Tuple
obtained by the index.
template <class T, class ... REST> class Tuple : public Tuple<REST ...> { public: using LeftMemberType = T; template <unsigned int INDEX> using MemberTypeIndexed = typename tuple::TupleIndexer<INDEX, T, REST ...>::TupleType::LeftMemberType; template <unsigned int INDEX> using TupleTypeIndexed = typename tuple::TupleIndexer<INDEX, T, REST ...>::TupleType; template <unsigned int INDEX> inline const MemberTypeIndexed<INDEX> & Get() const; // ... }; template <class T, class ... REST> template <unsigned int INDEX> inline const typename Tuple<T, REST ...>::template MemberTypeIndexed<INDEX> & Tuple<T, REST ...>::Get() const { return static_cast<const TupleTypeIndexed<INDEX> *>(this)->mMember; }
Tuple
.
template <class T, class ... REST> class Tuple : public Tuple<REST ...> { public: using LeftMemberType = T; template <unsigned int INDEX> using MemberTypeIndexed = typename tuple::TupleIndexer<INDEX, T, REST ...>::TupleType::LeftMemberType; template <unsigned int ... INDICES> using SubTupleTypeIndexed = Tuple<MemberTypeIndexed<INDICES> ...>; template <unsigned int ... INDICES> inline SubTupleTypeIndexed<INDICES ...> MakeTuple() const { return Tuple<MemberTypeIndexed<INDICES> ...>(Get<INDICES>() ...); } // ... };
Tuple
specific types with indices from a specified range. Those:
template <unsigned int A, unsigned int B> inline SubTupleTypeRanged<A, B> MakeSubTuple() const;
Tuple
returned by a range, not by an index. To do this, we use a technique similar to the method from the FlexFerrum article. Let's create a class that takes a range as template parameters (A and B) and stores a synonym for a certain class specified by indices from A to B. That class, in turn, will calculate for us the Tuple
type. Names for our classes will choose Range
and Indices
accordingly.
Indices
class that stores the appropriate type of Tuple
template:
template <class T, class ... REST> class Tuple; template <unsigned int ... INDICES> class Indices { public: template <class T, class ... REST> using SubTupleType = Tuple<typename Tuple<T, REST ...>::template MemberTypeIndexed<INDICES> ...>; protected: private: };
template <unsigned int A, unsigned int B> class Range { public: using RangeLesser = Range<A, B + (A < B ? - 1 : 1)>; template <unsigned int ... INDICES> using IndicesExtendable = typename RangeLesser::template IndicesExtendable<B, INDICES ...>; using Indices = IndicesExtendable<>; protected: private: }; template <unsigned int INDEX> class Range<INDEX, INDEX> { public: template <unsigned int ... INDICES> using IndicesExtendable = tuple::Indices<INDEX, INDICES ...>; using Indices = IndicesExtendable<>; protected: private: };
IndicesExtendable
pattern through the IndicesExtendable
pattern from the range with the length N - 1. Speaking in simpler language, we gradually tighten the range to a point with a step of one. If you observe the type inference process in the opposite direction, then at each iteration of the range expansion a new synonym for the IndicesExtendable
template IndicesExtendable
added by adding a new template parameter — another index to the synonym IndicesExtendable template from the previous iteration. Thus Range<1, 4>
will contain Indices = Indices<1, 2, 3, 4>
. This range class also works for the opposite case - Range<4, 1>
. This will give the type Indices<4, 3, 2, 1>
.
MakeSubTuple
method we need to somehow call the constructor call with the correct fields as arguments. You can do this by adding another MakeTuple
method that takes Indices
as a parameter. This is another way to specify the template method.
template <unsigned int ... INDICES> inline SubTupleTypeIndexed<INDICES ...> MakeTuple(const Indices<INDICES ...> &) const;
t.MakeTuple(Indices<1, 2, 3>) // , t.MakeTuple<1, 2, 3>()
MakeSubTuple
method. Simply we get the Indices we need from the Range, instantiate it on the stack or as a static variable and pass it to the advanced MakeTuple
template <class T, class ... REST> class Tuple : public Tuple<REST ...> { public: template <unsigned int A, unsigned int B> using SubTupleTypeRanged = typename Range<A, B>::Indices::template SubTupleType<T, REST ...>; template <unsigned int A, unsigned int B> inline SubTupleTypeRanged<A, B> MakeSubTuple() const { return MakeTuple(typename Range<A, B>::Indices()); } // ... };
MakeTuple
method that accepts a variable number of template parameter types (specified Indices
or Range
, otherwise it is an error at the compilation stage). The returned values of this method are clearer by example:
Tuple<char, int, double, float>::MakeTuple<Indices<3, 3>, Range<0, 1>, Indices<2>>
will return Tuple<float, float, char, int, double>
MakeTuple
can be combined into one, giving the parameter a default value. Hardly instantiating an empty class on the stack gives at least some significant overhead, so you should not worry about performance in this case.
Invoke
. It takes as a parameter everything to which you can apply the function call operator with the parameters corresponding to the fields of our Tuple
. Our apparatus for working with Tuple
already sufficiently developed to implement such a method without entering additional entities. One BUT - in order not to concretize the method with field indexes to pass to the function / functor / lambda / etc, we will have to use the same trick again as in the case of MakeSubTuple
.
template <class CALLABLE, unsigned int ... INDICES> inline void Invoke(CALLABLE & _function, const Indices<INDICES ...> &) { _function(Get<INDICES>()...); } template <class CALLABLE> inline void Invoke(CALLABLE & _function) { Invoke(_function, typename Range<0, mIndex>::Indices()); }
Tuple
class turns into a kind of core for the Bind
class. We give his announcement for completeness:
template <class ... ARGS> class Bind { public: using Function = std::function<void (ARGS ...)>; using Tuple = tuple::Tuple<ARGS ...>; Bind(Function _function, const ARGS & ... _args): mFunction(_function), mTuple(_args ...) { } void operator() () { mTuple.Invoke(mFunction); } private: Function mFunction; Tuple mTuple; };
CALLABLE
may have a return type, but the solution to this problem is beyond the scope of this article.
Tuple
class turned out to be quite fast, because no calculations are used in runtime.
Variadic Templates
allow you to implement a tuple in a very compressed number of lines of code. The only problem with duplication of code arises in the termination specialization of the Tuple - Tuple. , , , . Get . Get
. .
. Get<2>
Tuple<int, char>
, , Tuple
. "" , , , static_assert
.
Tuple
, Tuple<char, double, member1, member2>
, Get , . , .. C
, . Tuple
. , compile time
, , Serialize
. Tuple
.
. Tuple<int, char, double>
Tuple<char, double>
Tuple. . Tuple
, , , , , - , , .
Tuple
github .
readme, .
- , , append
, etc.
.
main.cpp , .
:
en.wikipedia.org/wiki/Variadic_template
habrahabr.ru/post/101430
class Tuple - Tuple. , , , . Get . Get
. .
. Get<2>
Tuple<int, char>
, , Tuple
. "" , , , static_assert
.
Tuple
, Tuple<char, double, member1, member2>
, Get , . , .. C
, . Tuple
. , compile time
, , Serialize
. Tuple
.
. Tuple<int, char, double>
Tuple<char, double>
Tuple. . Tuple
, , , , , - , , .
Tuple
github .
readme, .
- , , append
, etc.
.
main.cpp , .
:
en.wikipedia.org/wiki/Variadic_template
habrahabr.ru/post/101430
Tuple - Tuple. , , , . Get
. Get
. .
. Get<2>
Tuple<int, char>
, , Tuple
. "" , , , static_assert
.
Tuple
, Tuple<char, double, member1, member2>
, Get , . , .. C
, . Tuple
. , compile time
, , Serialize
. Tuple
.
. Tuple<int, char, double>
Tuple<char, double>
Tuple. . Tuple
, , , , , - , , .
Tuple
github .
readme, .
- , , append
, etc.
.
main.cpp , .
:
en.wikipedia.org/wiki/Variadic_template
habrahabr.ru/post/101430
Source: https://habr.com/ru/post/179153/