我正在与C ++中的模板一起探索常量表达式,但遇到了一个我无法理解的问题。
我想检查模板参数(在我的情况下为unsigned int)的值是否为零,但是即使我有一个static_assert确认它已经实际传递到零以下,编译器也从未认为该值为零。
我正在实现一个简单的(或者至少我以为如此)模板函数,该函数应该只汇总例如范围内的所有整数值。 5降为零。
应该对模板函数执行递归调用,直到达到零为止,然后应该停止,但是编译器从不认为模板参数值为零。
这是我有问题的功能:
template <unsigned int Value>
constexpr unsigned int sumAllValues()
{
static_assert (Value >= 0, "Value is less than zero!");
return Value == 0 ? 0 : Value + sumAllValues<Value - 1>();
}
它的调用方式如下:
constexpr unsigned int sumVals = sumAllValues<5>();
由于某种原因,编译器从不认为Value == 0,因此它将继续进行直到停止在static_assert上为止。如果删除断言,则编译器将继续执行直到达到最大实例化深度:
错误:模板实例化深度超过最大值900(使用-ftemplate-depth =增加最大值) 返回值== 0? 0:值+ sumAllValues();
上述功能在做什么?我不能检查模板参数本身的值吗?
我受到了我在Wikipedia上发现的一个例子的启发:
template<int B, int N>
struct Pow
{
// recursive call and recombination.
enum{ value = B*Pow<B, N-1>::value };
};
template< int B >
struct Pow<B, 0>
{
// ''N == 0'' condition of termination.
enum{ value = 1 };
};
int quartic_of_three = Pow<3, 4>::value;
请参阅参考资料:C++11
实际上,我确实有一个工作示例,该示例的构造方式与上面的Pow示例代码相同:
template <unsigned int Value>
constexpr unsigned int sumAllValues()
{
static_assert (Value > 0, "Value too small!");
return Value + sumAllValues<Value - 1>();
}
template <>
constexpr unsigned int sumAllValues<0>()
{
return 0;
}
它的调用方式与我有问题的函数相同:
constexpr unsigned int sumVals = sumAllValues<5>();
它也执行模板函数的递归调用,直到达到零为止。零情况专门用于中断递归。如果我输入5作为模板参数,此代码将起作用,并产生值15。
但是我想我可以通过遇到问题的功能来简化它。
我正在Qt 5.12.2的Linux(Ubuntu 18.04)上进行开发。
更新:
StoryTeller提出了一个可行的解决方案,该解决方案是使用C ++ 17功能“ if constexpr” 停止递归:
template <unsigned int Value>
constexpr unsigned int sumAllValues()
{
if constexpr (Value > 0)
return Value + sumAllValues<Value - 1>()
return 0;
}
答案 0 :(得分:7)
实例化函数模板的主体意味着实例化其使用的所有内容。 sumAllValues<0>
的主体看起来如何?就像这样:
template <>
constexpr unsigned int sumAllValues<0>()
{
static_assert (0 >= 0, "Value is less than zero!");
return Value == 0 ? 0 : 0 + sumAllValues<0 - 1>();
}
看到对sumAllValues<-1>
的呼叫吗?虽然不会对其进行评估,但它仍会在那里显示,因此必须实例化。但是Value
是未签名的,因此您会得到回绕。 (unsigned)-1
是一个非常大的无符号数字,不能小于零。因此,递归仍在继续,如果没有实现的限制,递归可能会无限期地继续。
具有专业化版本的版本没有与sumAllValues<0>
相同的功能主体,因此它永远不会尝试实例化sumAllValues<-1>
。递归实际上在0处停止。
在C ++ 17之前,专业化可能是获得所需功能的最短方法。但是,通过添加if constexpr
,我们可以将代码简化为一个函数:
template <unsigned int Value>
constexpr unsigned int sumAllValues()
{
if constexpr (Value > 0)
return Value + sumAllValues<Value - 1>()
return 0;
}
如果不满足条件, if constexpr
将完全丢弃其分支中的代码。因此,对于0
参数,该函数体内根本不会存在任何递归调用,因此无需进一步实例化。
答案 1 :(得分:3)
关于if constexpr
的工作方式的有趣细节(反转说明的条件):
if constexpr(Value == 0)
return 0;
return Value + sumAllValues<Value - 1>();
尽管if之后的代码将不被执行,但它仍然存在并且必须进行编译,并且您将陷入已经遇到的相同错误中。与之相反:
if constexpr(Value == 0)
return 0;
else
return Value + sumAllValues<Value - 1>();
现在,如果驻留在constexpr的else分支中,则当条件符合时,它将再次被完全丢弃,并且我们再也可以...