为什么递归 constexpr 模板值无法编译?

Why recursive constexpr template value does not compile?

本文关键字:编译 递归 constexpr 为什么      更新时间:2023-10-16

我正在使用 C++17 中的递归模板定义一种了解类型在类型列表中位置的方法。我尝试了两种方法:一种使用 constexpr 值,另一种使用 constexpr 函数。第二个使用if语句进行编译,而第一个使用三元运算符进行编译。

#include <type_traits>
#include <iostream>
template<typename Searching, typename First, typename...Others>
constexpr std::size_t index_of = std::is_same_v<Searching,First> ? 0 : 1 + index_of<Searching,Others...>;
template<typename Searching, typename First, typename...Others>
constexpr std::size_t IndexOf() {
if constexpr(std::is_same_v<Searching,First>)
return 0;
else return 1 + IndexOf<Searching,Others...>();
};
int main() {
std::cout << index_of<int, int> << std::endl; //does not compile
std::cout << IndexOf<int, int>() << std::endl; //compile
//both should return 0
return 0;
}

我的编译器 migw64 说:

模板参数数量错误(1,应至少为 2(
constexpr std::size_t index_of = std::is_same_v<搜索,第一> ? 0 : 1 + index_of<正在搜索,其他...>;

据我了解,三元运算符需要计算其两个操作数,因此不能在这种类型的递归中使用。

我是对的吗?如果是,为什么会这样?
谢谢。

我将从问题的末尾开始,然后继续工作。

据我了解,三元运算符需要评估其两个操作数

不。三元(意思是"由三个组成"(运算符有三个操作数,而不是两个。计算此运算符时,将计算三个操作数中的两个:条件和条件选取的操作数。

评估不是你的问题所在。

第一个使用三元运算符,不编译。

我想我明白这是为什么了。您正在将条件运算符的结果分配给std::size_t。为了进行编译,此结果的类型必须std::size_t或可转换为该类型。因此,编译器需要确定结果的类型。我找到了确定类型的规则。如果第二个或第三个操作数的类型为void,则第一条规则适用。因此,即使不会评估其中一个操作数,也必须知道它们的两种类型

好的,那么你的第三个操作数的类型是什么,不会被评估的那个?嗯,1 + index_of<int>,所以我们最好检查index_of<int>的声明.糟糕,我们需要两个参数。提示错误消息。

无论如何,这可能是您必须处理的事情,因为在"未找到"的情况下,对于任何一种方法,您都应该得到相同的错误(例如:index_of<unsigned, int, long, float>(。您可能已经注意到,默认错误消息不能很好地描述出了什么问题,因此您的模板最好专门解决这种情况,即使解决这种情况只是意味着提供更易于理解的编译器错误。

据我了解,三元运算符需要计算其两个操作数,因此不能在这种类型的递归中使用。

不是评估,而是实例化。实例化表达式std::is_same_v<Searching, First> ? 0 : 1 + index_of<Searching, Others...>时,必须实例化(不计算(所有三个操作数,因此由于实例化index_of<Searching, Others...>而发生错误。它类似于ifif constexpr之间的区别。如果您以第二种方式将if constexpr更改为if,它也不会编译。

解决方法是使用第二种方式(即函数模板(来初始化index_of,例如

template<typename Searching, typename First, typename... Others>
constexpr std::size_t IndexOf() {
if constexpr(std::is_same_v<Searching,First>)
return 0;
else return 1 + IndexOf<Searching,Others...>();
};
template<typename Searching, typename First, typename...Others>
constexpr std::size_t index_of = IndexOf<Searching, First, Others...>();

或使用模板专用化:

template<typename Searching, typename First, typename... Others>
constexpr std::size_t index_of = 1 + index_of<Searching, Others...>;
template<typename Searching, typename... Others>
constexpr std::size_t index_of<Searching, Searching, Others...> = 0;

如果需要更清晰的错误消息,可以将变量模板包装在类中并使用static_assert