user3882729
user3882729

Reputation: 1534

getting type index from a type list in C++

When I try and get the index of a type within a list of types using below, the code compiles and returns the correct value when the else clause is used. However when I skip the else clause and place the return getIndex<T, Ts...>(x + 1); just after the end of the if clause the code fails to compile as it continues to unwind getIndex<T, Ts...> recursively resulting in the error shown below. This is the case with gcc and clang. Is this expected?

#include <type_traits>
template <typename T, typename U, typename ...Ts>
constexpr int getIndex(int x = 0)
{
   if constexpr(std::is_same_v<T,U>)
   {
      return x;
   }
   else
   {
      return getIndex<T, Ts...>(x + 1);
   }
}

int main()
{
   return getIndex<int, float, double, int, char>();
}

Error when moving return outside of else

getI.cc: In instantiation of ‘int getIndex(int) [with T = int; U = char; Ts = {}]’:
getI.cc:9:32:   recursively required from ‘int getIndex(int) [with T = int; U = double; Ts = {int, char}]’
getI.cc:9:32:   required from ‘int getIndex(int) [with T = int; U = float; Ts = {double, int, char}]’
getI.cc:14:51:   required from here
getI.cc:9:32: error: no matching function for call to ‘getIndex<int>(int)’
    9 |       return getIndex<T, Ts...>(x + 1);
      |              ~~~~~~~~~~~~~~~~~~^~~~~~~
getI.cc:3:5: note: candidate: ‘template<class T, class U, class ... Ts> int getIndex(int)’
    3 | int getIndex(int x = 0)
      |     ^~~~~~~~
getI.cc:3:5: note:   template argument deduction/substitution failed:
getI.cc:9:32: note:   couldn’t deduce template parameter ‘U’
    9 |       return getIndex<T, Ts...>(x + 1);
      |              ~~~~~~~~~~~~~~~~~~^~~~~~~

Upvotes: 3

Views: 1010

Answers (2)

dwcanillas
dwcanillas

Reputation: 3651

Peter Taran's answer has already touched on why the code compiles with the else clause, but he didn't really touch on why the compilation error occurs or how to fix it. Here is a working version of your code:

#include <type_traits>

template <typename T>
constexpr int getIndex(int x = 0)
{
   return -1;
}

template <typename T, typename U, typename ...Ts>
constexpr int getIndex(int x = 0)
{
   if constexpr (std::is_same_v<T,U>)
   {
      return x + 1;// NOTE: you have a bug on this line
   }
   else
   {
      return getIndex<T, Ts...>(x + 1);
   }
}

int main()
{
   constexpr int index = getIndex<int, float, double, int, char>();

   return 0;
}

Admittedly, I haven't used variadic templates much, but from what I am understanding here, the main problem is in this snippet of your compilation errors:

getI.cc:9:32: error: no matching function for call to ‘getIndex<int>(int)’

Despite having a base case defined in your recursive template, it seems like variadic templates are required to fully unpack. Because of that, you need a getIndex<T> defined, even if the code is not reached in your example. Adding the single argument getIndex<T> allowed the code to compile and run.

EDIT: This post is also related, a good read, and is also another possible solution to your issue.

Upvotes: 3

Peter  Taran
Peter Taran

Reputation: 150

As I realized, broken code is:

template <typename T, typename U, typename ...Ts>
constexpr int getIndex(int x = 0)
{
   if constexpr(std::is_same_v<T,U>) {
      return x;
   }
   return getIndex<T, Ts...>(x + 1);
}

Accoring to this article:

In a constexpr if statement, the value of condition must be an expression contextually converted to bool, where the conversion is a constant expression. If the value is true, then statement-false is discarded (if present), otherwise, statement-true is discarded.

Note that nothing is said about discarding statements outside if-else clause, so you should expect that recusive getIndex's will be instantiated.

Upvotes: 2

Related Questions