Template Parameter Packs Access Nth Type and Nth Element

template parameter packs access Nth type and Nth element

C++11 doesn't have corresponding operators which is the reason they are proposed. With C++11 you'll need to either extract the corresponding information yourself or use a class which already does the necessary operation. The easiest approach is probably to just use std::tuple<T...> which already implements the corresponding logic.

If you wonder how std::tuple<T...> currently implements these operations: it is basically an exercise in functional programming using a fairly bad functional programming notation. Once you know how to get the n-th type of the sequence, getting the n-th element using inheritance from base classes parameterized on index and type is fairly trivial. Implementing something like tuple_element<N, T...> could look something like this:

template <int N, typename... T>
struct tuple_element;

template <typename T0, typename... T>
struct tuple_element<0, T0, T...> {
typedef T0 type;
};
template <int N, typename T0, typename... T>
struct tuple_element<N, T0, T...> {
typedef typename tuple_element<N-1, T...>::type type;
};

The actual more challenging bit in implementing something like std::tuple<T...> is conjuring up a list of indices so you got a parallel list of type and integers which can then be expanded, e.g., for a list of base classes using something like (how the internal details look exactly will differ but the basic idea of having a parallel parameters packs for the types and their indices will be somehow there):

template <typename... T, int... I>
class tuple_base<tuple_types<T...>, tuple_indices<I...>>:
public tuple_field<T, I>... {
};

C++11 indexing template parameter packs at runtime in order to access Nth type

C++ is a statically​ typed language. As such the type of all variables needs to be known at compile time (and cannot vary). You want a type that depends on a runtime value. Luckily C++ also features dynamic typing of objects.

Warning: all code in this answer serves only for demonstration of the basic concept/idea. It's missing any kind of error handling, sane interfaces (constructors...), exception safety, ... . So don't use for production, consider using the implementations​ available from boost.

To use this feature you need what's called a polymorphic base class: a class with (at least) one virtual member function from which you derive further classes.

struct value_base {
// you want to be able to make copies
virtual std::unique_ptr<value_base> copy_me() const = 0;
virtual ~value_base () {}
};

template<typename Value_Type>
struct value_of : value_base {
Value_Type value;

std::unique_ptr<value_base> copy_me() const {
return new value_of {value};
}
};

You can then have a variable with static type of pointer or reference to that base class, which can point to/reference objects from both the base class as well as from any of those derived classes. If you have a clearly defined interface, then encode that as virtual member functions (think of Shape and area (), name (), ... functions) and make calls through that base class pointer/reference (as shown in the other answer). Otherwise use a (hidden) dynamic cast to obtain a pointer/reference with static type of the dynamic type:

struct any {
std:: unique_ptr<value_base> value_container;

// Add constructor

any(any const & a)
: value_container (a.value_container->copy_me ())
{}
// Move constructor

template<typename T>
T & get() {
value_of<T> * typed_container
= dynamic_cast<value_of<T> *>(value_container.get();)
if (typed_container == nullptr) {
// Stores another type, handle failure
}
return typed_container->value;
}

// T const & get() const;
// with same content as above
};

template<typename T, typename... Args>
any make_any (Args... && args) {
// Raw new, not good, add proper exception handling like make_unique (C++14?)
return {new T(std:: forward<Args>(args)...)};
}

Since object construction is done at runtime the actual type of the pointed to/referenced object may depend on runtime values:

template<typename T>
any read_and_construct (std:: istream & in) {
T value;
// Add error handling please
in >> value;
return make_any<T>(std:: move (value));
}

// ...

// missing: way of error handling
std::map<int, std:: function<any(std:: istream &)>> construction_map;
construction_map.insert(std::make_pair(1, read_and_construct<double>));
// and more
int integer_encoded_type;
// error handling please
cin >> integer_encoded_type;
// error handling please
any value = construction_map [integer_encoded_type] (cin);

As you may have noticed above code uses also a clearly defined interface for construction. If you don't intend to do lots of different things with the returned any objects, potentially storing them in various data structures over great parts of the time your program is running, then using an any type is most likely overkill and you should just put the type dependent code into those construction functions, too.

A serious drawback of such an any class is its generality: it's possible to store just about any type within it. This means that the (maximum) size of the (actually) stored object is not known during compilation, making use of storage with automatic duration (the "stack") impossible (in standard C++). This may lead to expensive usage of dynamic memory (the "heap"), which is considerably slower than automatic memory. This issue will surface whenever many copies of any objects have to be made, but is probably irrelevant (except for cache locality) if you just keep a collection of them around.

Thus, if you know at compile time the set of types which you must be able to store, then you can (at compile time) compute the maximum size needed, use a static array of that size and construct your objects inside that array (since C++11 you can achieve the same with a (recursive template) union, too):

constexpr size_t max_two (size_t a, size_t b) {
return (a > b) ? a : b;
}

template<size_t size, size_t... sizes>
constexpr size_t max_of() {
return max_two (size, max_of<sizes>());
}

template<typename... Types>
struct variant {
alignas(value_of<Types>...) char buffer[max_of<sizeof (value_of<Types>)...>()];
value_base * active;

// Construct an empty variant
variant () : active (nullptr)
{}

// Copy and move constructor still missing!

~variant() {
if (active) {
active->~value_base ();
}
}

template<typename T, typename... Args>
void emplace (Args... && args) {
if (active) {
active->~value_base ();
}
active = new (buffer) T(std:: forward<Args>(args)...);
}
};

When using variadric template, how to get nth arguments type?

You can get the type from parameter pack with the help of a recursive inheriting type trait.

template<unsigned int TIndex, typename ...TColValue>
struct get_nth_from_variadric_type;

template<unsigned int TIndex, typename Head, typename... Tail >
struct get_nth_from_variadric_type<TIndex, Head, Tail...>
: get_nth_from_variadric_type<TIndex-1, Tail...> { };

template<typename Head, typename... Tail>
struct get_nth_from_variadric_type<0, Head, Tail...> {
using type = Head;
};

template<unsigned int TIndex, typename ...TColValue>
using get_nth_from_variadric = typename get_nth_from_variadric_type<TIndex, TColValue...>::type;

Then use it like

template<typename ...TColValue>
class ResultRow
{
public:
template<unsigned int TIndex>
get_nth_from_variadric<TIndex, TColValue...> GetValue() const
{
return valueGetter<get_nth_from_variadric<TIndex, TColValue...> >();
}
};

How to use and access a template parameter pack of parameter packs

I would use something similar to what the STL ended up doing: Pass packs of packs as a pack of tuple.

Take emplace_back in a pair for example:

stuct Widget {
int a;
std::string name = {};
};

std::pair<Widget, Widget>{
std::piecewise_construct,
std::forward_as_tuple(12, "values"),
std::forward_as_tuple(31)
};

Here the first parameter tell that you want piecewise construct, so each argument will act as a "pack" that will be expanded into the members of the pair. In this case, the elements of the pair was construct as if:

Widget first{12, "values"};
Widget second{31};

In your case, it seems you always want to construct N components. So the solution would be:

template<typename Component, typename Tuple>
void addComponentUnpack(Tuple&& tuple) {
std::apply([](auto&&... args) {
addComponent<Component>(std::forward<decltype(args)>(args)...);
}, std::forward<Tuple>(tuple));
}

template<typename... Components, typename... Tuples>
void addComponents(Tuples&&... tuples) {
(addComponentUnpack<Components, Tuples>(std::forward<Tuples>(tuples)), ...);
}

So for each Component, you have a corresponding Tuple in the packs Components and Tuples. Then, each tuples are sent to a function that construct a single component of type Component using the tuple Tuple.

This example uses C++17 features such as fold expressions and std::apply but it can be implemented in C++14 with a bit more code.


If however you don't wish to use tuples as parameters, you could alway recieve instances of your types directly:

template<typename... Components>
void addComponents(Components&&... components) {
// ...
}

It's usage would be like:

addComponents(
PositionComponent{12, 43},
CollisionComponent{},
SpriteComponent{"texture name"}
);

// or

addComponents<PositionComponent, CollisionComponent, SpriteComponent>(
{12, 42},
{},
{"texture name"}
);

Of course, that would require to move component from the parameters into the entity, which may not be entirely free, depending on the case.

Get the Nth type of variadic template templates?

You can use std::tuple:

#include<tuple>

template<typename... Args>
class MyClass
{
typename std::tuple_element<0, std::tuple<Args...> >::type mA;
};

Folding of template parameter packs in pre C++17: idiomatic approach

Are any of these variations (or other variations) considered "the idiomatic one"?

I would say yes.

Are there any subtleties/differences between them that one would need to take care with?

There are mostly equivalent equivalent, but

foo3 and foo4 require #include <initializer_list>
whereas foo1 and foo2 don't.

Does parameter packs carry a set limit?

I don't think that a separate limit is necessary. The number of elements is dictated by the site of instantiation and how many template arguments can be passed to a template:

  • There is a limit of the number of arguments to a function call (recommended minimum: 256).
  • There is a limit of the number of template parameters in a template declaration (recommended minimum: 1024)

The first is definitively applicable, and the second may apply if the implementation has to expand a pack into a full declaration internally. (But I do not know for sure; I do not write C++ compilers.)

Split parameter pack in 0 ... N-1 and Nth element

Ok, let's be creative. I`m sure there is a more "standard" way to do this, but I kinda like this solution ;)

http://coliru.stacked-crooked.com/a/25a3fa276e56cd94

The core idea is to recursively rotate the arguments until we can split out the (formerly) last argument.

template <size_t N>
struct MoreFunHelper
{
template <class Head, class... Tail>
static void RotateLeft(Head head, Tail... tail)
{
MoreFunHelper<N - 1>::RotateLeft(tail..., head);
}
};

template <>
struct MoreFunHelper<0>
{
template <class Head, class... Tail>
static void RotateLeft(Head head, Tail... tail)
{
Fun(CalcFoo(tail...), head);
}
};

template< class... T >
void MoreFun(T... args)
{
MoreFunHelper<sizeof...(T) - 1>::RotateLeft(args...);
}

So if we start with the arguments

1 2 3 4 5

it rotates them 4 times:

2 3 4 5 1
3 4 5 1 2
4 5 1 2 3
5 1 2 3 4

Now we can split them smoothly into [5] and [1 2 3 4], which is exactly what we want. At this point, the recursion stops and just calls the functions CalcFoo and Fun.

Multiple Variadic Parameter Pack for Template Class

In the discussion comments you expressed a willingness to consider some kind of indirection, or "a wrapper of some kind for the attribute list".

A lightweight std::tuple-based wrapper, together with specialization, might work here:

template <typename attribute_tuple, APITypes APIType,
typename policy_tuple> class IShader;

template <AttributeType... Attributes, APITypes APIType,
class... Policies>
class IShader<std::tuple<Attributes...>, APIType,
std::tuple<Policies...>> : public Policies... {

// ...

};

The goal here is to use a template instance along the lines of:

IShared<std::tuple<Attribute1, Attribute2>, APITypeFoo,
std::tuple<Policy1, Policy2>> ishared_instance;

And cross your fingers that this is going to match the specialized template declaration, at which point both parameter packs are available for the template specialization to use, individually.



Related Topics



Leave a reply



Submit