检查C ++模板值零失败

时间:2019-06-27 05:09:38

标签: c++ templates recursion instantiation

我正在与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;
    }

2 个答案:

答案 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)

除了StoryTelleranswer

关于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分支中,则当条件符合时,它将再次被完全丢弃,并且我们再也可以...