Overview
In A Template Argument Extender C++ template metaprogramming was used to repeat a single template parameter type N times. This article outlines how such can be enhanced to repeat a set of template parameter types N types.
Defining The Problem
The previous article outlined how to use C++ template metaprogramming (C++ TMP) to create a template class having a single type repeated N times as a parameter. For example, the type:
would become:
whereas:
n_tuple<2, int> // 2
n_tuple<3, int> // 3
would become:
std::tuple<int, int> // 2
std::tuple<int, int, int> // 3
Unfortunately, if one wanted to repeat a list of types:
to produce:
one was out of luck with my previous article. But as you might have guessed it is possible to easily write C++ TMP code to handle such with a little cleverness! 🙂
Defining A Placeholder Type
One of the weird clever things one does when writing C++ TMP code is creating types that are not at all used at run-time. These types are simply placeholders so that the C++ TMP code transformations can be made to work. In this article, a placeholder type is defined and used purely for compile-time only code transformations. The purpose of the placeholder is to represent a type list for a set of template parameters:
struct tl { };
The reason one needs such a type is because template parameter packs:
- are not types (e.g., one cannot hold a template parameter pack in a typedef),
- cannot have their parameters directly manipulated, and,
- they can only be expanded (e.g., using
...
) or queried for their size (e.g., usingsizeof...
).
However when using a placeholder type template parameter packs:
- can be held as the template parameters of the placeholder types,
- can be passed around and stored in typedefs via the placeholder type since the placeholder type is a type,
- can be (indirectly) queried/manipulated using template template parameters and partial template specialization,
- can even have a concrete run-time implementation if needed.
Thus, by using a placeholder type, one can circumvent the limitations of template parameter packs. Some of these benefits of handling template parameter packs by using a placeholder type will be apparent in the code below.
Defining A Template Alias
Although one cannot have template typedefs in C++, the C++11 standard introduced template aliases using the using
keyword. The definition of the above n_tuple
as a template alias is:
using n_tuple = typename repeater<std::tuple, N, tl<Args...>>::type;
where N
is the number of times to repeat Args...
as the parameter list for std::tuple
. Notice Args...
is passed using the tl
placeholder type defined above.
So all that is left is to define the repeater
class.
Defining The repeater
Class
General Template Definition
The repeater
class is where the real work is done. For the repeater class to work, one needs:
- a template template parameter of the type that when provided the repeated template parameters will determine the type of the final result,
- the number of times to repeat the types,
N
, - the types to be repeated (passed using the placeholder type to keep such separate from the actual repeated results), and,
- a temporary list of already repeated arguments (initially empty).
Thus the general definition of repeater
(which must appear before any specializations) is:
template <typename...> class ResultHolder,
unsigned N,
typename Repeated,
typename... ArgsList
>
struct repeater;
From the definition of n_tuple
:
ResultHolder
is set tostd::tuple
,N
is the number of times to repeat the parameters contained inRepeated
,Repeated
needs to be separated out from the wrapper placeholder type (i.e., by using a template template type in the specializations) from the parameters it holds, and,ArgsList
is initially empty but will hold the recursive repeated-expansion of the template arguments.
Recursive Case
This case applies when N > 0
. When such is true, we'll append ToBeRepeated...
to ArgsList...
, decrement N
, and re-invoke repeater
:
template <typename...> class ResultHolder,
unsigned N,
template <typename...> class Holder,
typename... ToBeRepeated,
typename... ArgsList
>
struct repeater<
ResultHolder,
N,
Holder<ToBeRepeated...>,
ArgsList...
>
{
typedef
typename repeater<
ResultHolder,
N-1,
Holder<ToBeRepeated...>,
ArgsList..., ToBeRepeated...
>::type
type
;
};
Base Case
This case applies when N == 0
. In this case ArgsList...
is now the complete list of template parameter types that must be placed in ResultHolder
and returned via the type
typedef:
template <typename...> class ResultHolder,
template <typename...> class Holder,
typename... ToBeRepeated,
typename... ArgsList
>
struct repeater<
ResultHolder,
0U,
Holder<ToBeRepeated...>,
ArgsList...
>
{
typedef ResultHolder<ArgsList...> type;
};
As done within this article, one should build up ArgsList...
before placing it in ResultHolder
to avoid any potential errors with ResultHolder
requiring a certain minimum of type parameters.
Testing The Solution
The resulting code can now be tested (again I am using code specific to GCC as I did in the original article):
// Definition of tl
// Definition of repeater
// Definition of n_tuple
#include <string>
#include <typeinfo>
#include <cstdlib>
#include <cxxabi.h> // GCC-specific
template <typename T>
std::string demangled_name_of_type() // GCC-specific
{
// NOTE: See GCC Manual Chapter 27.
// http://gcc.gnu.org/onlinedocs/libstdc++/
int status;
char *name = abi::__cxa_demangle(typeid(T).name(), 0, 0, &status);
std::string realname(name);
free(name);
return realname;
}
#include <iostream>
int main()
{
using namespace std;
cout
<< demangled_name_of_type<n_tuple<0>>() << endl
<< demangled_name_of_type<n_tuple<0,int>>() << endl
<< demangled_name_of_type<n_tuple<1,int>>() << endl
<< demangled_name_of_type<n_tuple<2,int>>() << endl
<< demangled_name_of_type<n_tuple<0,char,short,long>>() << endl
<< demangled_name_of_type<n_tuple<1,char,short,long>>() << endl
<< demangled_name_of_type<n_tuple<2,char,short,long>>() << endl
;
}
For example under Gentoo Linux I compiled main.cxx above using GCC v4.7 (snapshot 20111217) using this command:
-Xlinker -R /usr/lib/gcc/x86_64-pc-linux-gnu/4.7.0-alpha20111217 \
main.cxx
which produces this output when run:
std::tuple<>
std::tuple<>
std::tuple<int>
std::tuple<int, int>
std::tuple<>
std::tuple<char, short, long>
std::tuple<char, short, long, char, short, long>
$
The goal of having a list-of-types extended automatically has been reached!
Closing Comments
As is typical when one revisits old code, one improves the previous code. Such is true here: the above code is simpler and easier-to-understand than my previous article's code. The use of a template alias further enhances readability and end-user usability. The alias also avoids the need for a special class simply to properly invoke the repeater
class.
Newcomers to C++ TMP should note that the placeholder type used above could be any template class type accepting 0 or more types as template parameters. The placeholder's only use is to permit the extraction of its template parameters using partial template specialization. If combined with other C++ TMP code then not hard-coding the placeholder type is especially convenient: should one use repeater
directly, one does not need to extract the parameters from one template class type and then place those types into a special placeholder type just to use repeater
.
If one is using repeater
directly, one can pass an initial set of template parameters for ArgsList...
. These parameters will not be repeated but will be used as the first parameters of the Result
type regardless of the value of N
.
Finally note that one could have placed ArgsList...
within a placeholder type as well. I wanted it to be easy to pass in a list of mandatory initial template parameters when using repeater
directly. If one wanted to process multiple parameter lists independently then using a placeholder type would be essential.
Happy Coding!