Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unroll template specializations

I am trying to get the type at the specified index in a parameter pack using template metaprogramming. I have the code below but for some reason it always returns an int, could someone tell me what I am doing wrong?

#include <string>
#include <iostream>
using std::cout;
using std::endl;
using std::string;

template <int current_index, typename... Vs>
struct TypeForIndex {};
template <int current_index, typename Head, typename... Tail>
struct TypeForIndex<current_index, Head, Tail...> : private TypeForIndex<current_index + 1> {
    using type = Head;
};
template <int current_index, typename Tail>
struct TypeForIndex<current_index, Tail> {
    using type = Tail;
};

int main() {

    TypeForIndex <2, int, double, string>::type a {"hello"};
    cout << a << endl;

    return 0;
}

The code above should return string as the type for a but somehow it is always an int

like image 538
Curious Avatar asked Jan 06 '23 09:01

Curious


1 Answers

TypeForIndex<2, int, double, string>

ok, pattern matching time. First, it clearly matches

template <int current_index, typename... Vs>
struct TypeForIndex {};

so no error. Does it match any other specializations?

A:

template <int current_index, typename Head, typename... Tail>
struct TypeForIndex<current_index, Head, Tail...>

B:

template <int current_index, typename Tail>
struct TypeForIndex<current_index, Tail>

Well, it matches (A) and not (B).

With (A), current_index is 2, Head is int and Tail... is double, std::string.

template <int current_index, typename Head, typename... Tail>
struct TypeForIndex<current_index, Head, Tail...> : private TypeForIndex<current_index + 1> {
    using type = Head;
};

Now, the private TypeForIndex<current_index + 1> is pretty much useless. It always only matches the primary specialization, which has an empty body, and it is private so nobody will notice it. We can remove it without changing your program's behavior at all.

template <int current_index, typename Head, typename... Tail>
struct TypeForIndex<current_index, Head, Tail...> {
    using type = Head;
};

As noted above, Head is int. So we get type=int.

And that's it. That is why type is int.

...

What you are doing wrong is almost everything? Other than compiling (ie, the primary specialization exists that matches the signature), the code you provide has nothing to do with what you describe in your text. Even current_index+1 is a string I wouldn't expect to exist in code that does what your text describes.

Throwing out everything except the primary specialization, this works:

template <typename Head, typename... Tail>
struct TypeForIndex<0, Head, Tail...> {
  using type = Head;
};
template <int current_index, typename Head, typename... Tail>
struct TypeForIndex<current_index, Head, Tail...>:
  TypeForIndex<current_index-1, Tail...>
{};

and it properly lacks a definition for type if you pass a too-large index.

I would also use a size_t not an int.

like image 116
Yakk - Adam Nevraumont Avatar answered Jan 08 '23 21:01

Yakk - Adam Nevraumont