50

How is it possible to create a recursive variadic template to print out the contents of a paramater pack? I am trying with this, but it fails to compile:

template <typename First, typename ...Args>
std::string type_name () {
    return std::string(typeid(First).name()) + " " + type_name<Args...>();
}
std::string type_name () {
    return "";
}

How shall I end the recursion?

Rupesh Yadav
  • 12,096
  • 4
  • 53
  • 70
Gabor Marton
  • 2,039
  • 2
  • 22
  • 33

6 Answers6

51

There's actually a very elegant way to end the recursion:

template <typename Last>
std::string type_name () {
    return std::string(typeid(Last).name());
}

template <typename First, typename Second, typename ...Rest>
std::string type_name () {
    return std::string(typeid(First).name()) + " " + type_name<Second, Rest...>();
}

I initially tried template <typename Last> and template <typename First, typename ...Rest> but that was considered ambiguous (Rest can be zero elements). This question then showed me the definitive solution: Compilation Error on Recursive Variadic Template Function


Note, to avoid a bit of code duplication, you could also do:

template <typename Last>
std::string type_name () {
    return std::string(typeid(Last).name());
}

template <typename First, typename Second, typename ...Rest>
std::string type_name () {
    return type_name<First>() + " " + type_name<Second, Rest...>();
}
Community
  • 1
  • 1
Aberrant
  • 3,423
  • 1
  • 27
  • 38
  • 2
    Note that this doesn't work if the parameter pack is empty, but otherwise a very nice solution. – zennehoy Jul 12 '16 at 15:14
  • 2
    just add a 3rd overload for the empty case (if you want one) – Mordachai Aug 09 '16 at 19:38
  • 1
    I'd personally use this solution but, for completeness' sake, C++11 allows you to use `std::enable_if` to specify that you only want the variadic case matched if there are more than two arguments: `typename std::enable_if= 1, int>::type = 0` – user862857 Sep 11 '19 at 15:28
42

You need to use partial specialisation to end the recursion, but since you can't partially specialise free functions in C++, you need to create an implementation class with a static member function.

template <typename... Args>
struct Impl;

template <typename First, typename... Args>
struct Impl<First, Args...>
{
  static std::string name()
  {
    return std::string(typeid(First).name()) + " " + Impl<Args...>::name();
  }
};

template <>
struct Impl<>
{
  static std::string name()
  {
    return "";
  }
};

template <typename... Args>
std::string type_name()
{
    return Impl<Args...>::name();
}

int main()
{
  std::cout << type_name<int, bool, char, double>() << std::endl; // "i b c d"
  return 0;
}

That first declaration of Impl is just a workaround for a shortcoming in g++ 4.6 (and below). It won't be necessary once it implements variadic templates correctly.

Check it out in action at ideone.com

Peter Alexander
  • 53,344
  • 14
  • 119
  • 168
  • The workaround link you cite is dead. Can you elaborate the correct way to do this without a buggy g++? – cdhowie Nov 25 '14 at 17:30
  • Although this answer is correct, it is outdated. A simpler approach is possible, see other answers. – Johannes Mar 24 '17 at 12:14
  • This technique also works when the template arguments are templates (variadic template consisting of template template arguments) – saxbophone May 10 '21 at 08:45
31

C++17's if constexpr allows you to do this in one template declaration which is, unlike a lot of the older solutions, pretty easy to understand:

template <typename T, typename ...Args>
std::string type_name() {
  if constexpr (!sizeof...(Args)) {
    return std::string(typeid(T).name());
  } else {
    return std::string(typeid(T).name()) + " " + type_name<Args...>();
  }
}
user862857
  • 483
  • 5
  • 6
12

As an alternative to non-existing partial specialization for functions, you can use overloading on a typifier class:

#include <string>
#include <iostream>
#include <typeinfo>

template <unsigned int N> struct NumberToType { };

template <typename T>
std::string my_type_name(NumberToType<0> = NumberToType<0>())
{
  return std::string(typeid(T).name());
}

template <typename T, typename ...Args>
std::string my_type_name(NumberToType<sizeof...(Args)> = NumberToType<sizeof...(Args)>())
{
  return std::string(typeid(T).name()) + " " + my_type_name<Args...>(NumberToType<sizeof...(Args)-1>());
}

int main()
{
  std::cout << my_type_name<int, double, char>() << std::endl;
}
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
7

As an alternative, you can unpack the parameter pack in-place as in the following example:

#include<string>
#include<iostream>
#include<typeinfo>

template <typename T, typename ...Args>
std::string type_name () {
    std::string str = typeid(T).name();
    int arr[] = { 0, (str += std::string{" "} + typeid(Args).name(), 0)... };
    (void)arr;
    return str;
}

int main() {
    auto str = type_name<int, double, char>();
    std::cout << str << std::endl;
}

Recursion is not required actually to do that.

skypjack
  • 49,335
  • 19
  • 95
  • 187
  • compilation error due `constexpr` function declaration (VS2015 Update 3). All will compile, if it will be removed – Ivan Kush Sep 22 '16 at 11:40
  • @skypkack, i compile with `/std:c++latest` , i.e. supports `C++17`. Errors are: `Error C3250 'str': declaration is not allowed in 'constexpr' function body ` and `Error C3250 'arr': declaration is not allowed in 'constexpr' function body ` – Ivan Kush Sep 22 '16 at 12:28
  • @Macias nothing special, this is the pre-C++17 alternative to fold expressions. It unrolls the parameter pack and _does things_. – skypjack Jun 28 '19 at 18:38
  • @skypjack Its nice :) You can event write the body of your function like this: `return ( std::string(typeid(Args).name() + std::string(" ")) + ...);` You should then remove first template parameter T. – Macias Jun 28 '19 at 19:52
  • It only works with C++17 - working example: https://wandbox.org/permlink/JEXUzCZbub57OFyy (I can't edit previous comment) – Macias Jun 28 '19 at 20:01
  • 1
    @Macias yep, but the Q/A is from 2016. ;-) – skypjack Jun 28 '19 at 20:03
  • @skypjack Right, but You could update your answer with a solution from c ++ 17 as a curiosity :) – Macias Jun 28 '19 at 21:28
4

Use C++17 fold expression:

template <typename ...Args>
std::string type_name () {
    return (std::string(typeid(Args).name()) + " " + ...);
}
Majid Kamali
  • 41
  • 1
  • 4