从 C++ 中的类型列表中获取类型索引

问题描述

当我尝试在下面使用的类型列表中获取类型的索引时,代码会在使用 else 子句时编译并返回正确的值。但是,当我跳过 else 子句并将 return getIndex<T,Ts...>(x + 1); 放在 if 子句末尾之后,代码无法编译,因为它继续递归展开 getIndex<T,Ts...> 导致如下所示的错误gccclang 就是这种情况。这是预期的吗?

#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>();
}

将 return 移到 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,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);
      |              ~~~~~~~~~~~~~~~~~~^~~~~~~

解决方法

Peter Taran 的回答已经提到了为什么代码用 else 子句编译,但他没有真正提到为什么会出现编译错误或如何修复它。这是您的代码的工作版本:

#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;
}

诚然,我没有经常使用可变参数模板,但根据我在这里的理解,主要问题在于您的编译错误片段:

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

尽管在递归模板中定义了基本情况,但似乎需要可变参数模板才能完全解包。因此,您需要定义一个 getIndex<T>,即使在您的示例中未达到该代码。添加单个参数 getIndex<T> 允许代码编译和运行。

编辑:This post 也是相关的,很好读,也是您问题的另一种可能解决方案。

,

正如我意识到的,损坏的代码是:

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

根据此article

在 constexpr if 语句中,condition 的值必须是一个 表达式上下文转换为 bool,其中转换是 常量表达式。如果值为真,则 statement-false 为 丢弃(如果存在),否则丢弃 statement-true。

请注意,没有提到丢弃 if-else 子句之外的语句,因此您应该期望递归 getIndex 将被实例化。