An Enhanced Template Parameter Extender

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:

n_tuple<0, int>

would become:

std::tuple<>

whereas:

n_tuple<1, int> // 1
n_tuple<2, int> // 2
n_tuple<3, int> // 3

would become:

std::tuple<int> // 1
std::tuple<int, int> // 2
std::tuple<int, int, int> // 3

Unfortunately, if one wanted to repeat a list of types:

n_tuple<2, int, double>

to produce:

std::tuple<int, double, int, double>

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:

template <typename... T>
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., using sizeof...).

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:

template <unsigned N, typename... Args>
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 <
  template <typename...> class ResultHolder,
  unsigned N,
  typename Repeated,
  typename... ArgsList
>
struct repeater;

From the definition of n_tuple:

  1. ResultHolder is set to std::tuple,
  2. N is the number of times to repeat the parameters contained in Repeated,
  3. 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,
  4. 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 <
  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 <
  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):

#include <tuple>

// 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:

g++-4.7.0-alpha20111217 \
  -Xlinker -R /usr/lib/gcc/x86_64-pc-linux-gnu/4.7.0-alpha20111217 \
  main.cxx

which produces this output when run:

$ ./a.out
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!

Leave a Reply

Your email address will not be published. Required fields are marked *