为什么递归constexpr模板值不能编译?

时间:2018-06-23 20:10:15

标签: c++ templates c++17 constexpr if-constexpr

我正在定义一种方法,该方法使用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 <正在搜索,其他...>;

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

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

2 个答案:

答案 0 :(得分:1)

我将从问题的结尾开始,然后逐步解决。

  

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

不。三元运算符(意​​思是“由三个组成”)具有三个操作数,而不是两个。在评估此运算符时,将评估三个操作数中的两个:条件和条件选择的操作数。

评估不是您的问题所在。

  

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

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

好,那么您的第三个操作数是什么类型,将不会被求值的那个呢?好吧,它是1 + index_of<int>,所以我们最好检查一下index_of<int>的声明。糟糕,我们需要两个参数。提示错误消息。

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

答案 1 :(得分:1)

  

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

不是评估,而是实例化。实例化表达式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