【问题标题】:C++14: deduced (auto) return types from constexpr with ternary expressionsC++14:使用三元表达式从 constexpr 推导出(自动)返回类型
【发布时间】:2015-03-05 01:49:49
【问题描述】:

我正在 C++14 中试验 constexpr 函数。以下代码计算阶乘按预期工作:

template <typename T>
constexpr auto fact(T a) {
    if(a==1)
        return 1;
    return a*fact(a-1);
}

int main(void) {
    static_assert(fact(3)==6,  "fact doesn't work");
}

当用clang编译如下:

> clang++ --version
clang version 3.5.0 (tags/RELEASE_350/final)
Target: x86_64-unknown-linux-gnu
Thread model: posix
> clang++ -std=c++14 constexpr.cpp

但是,当我将 fact 定义更改为使用三元 ? 运算符时:

template <typename T>
constexpr auto fact(T a) {
    return a==1 ? 1 : a*fact(a-1);
}

我收到以下编译器错误:

> clang++ -std=c++14 constexpr.cpp
constexpr.cpp:12:31: fatal error: recursive template instantiation exceeded maximum depth of
      256
    return a==T(1) ? T(1) : a*fact(a-1);
        ... snip ...
constexpr.cpp:16:19: note: in instantiation of function template specialization 'fact<int>'
      requested here
    static_assert(fact(3)==6,  "fact doesn't work");

如果我明确声明返回类型 T(而不是使用 auto 推断返回类型),问题就解决了

template <typename T>
constexpr T fact(T a) {
    return a==1 ? 1 : a*fact(a-1);
}

如果我删除模板参数,模式会重复(三元版本失败,if 版本有效)

// this works just fine
constexpr auto fact(int a) {
    if(a==1)
        return 1;
    return a*fact(a-1);
}

而这失败了

constexpr auto fact(int a) {
    return a==1 ? 1 : a*fact(a-1);
}

出现以下错误

> clang++ -std=c++14 constexpr.cpp
constexpr.cpp:16:25: error: function 'fact' with deduced return type cannot be used before it
      is defined
    return a==1 ? 1 : a*fact(a-1);
                        ^
constexpr.cpp:15:16: note: 'fact' declared here
constexpr auto fact(int a) {
               ^
constexpr.cpp:20:26: error: invalid operands to binary expression ('void' and 'int')
    static_assert(fact(3)==6,  "fact doesn't work");
                  ~~~~~~~^ ~
2 errors generated.

这是怎么回事?

【问题讨论】:

    标签: c++ ternary-operator c++14 constexpr


    【解决方案1】:

    对三元表达式求值的结果类型是common type of its second and third arguments

    通过让编译器推导出返回类型,您可以强制编译器将这两个参数都计算为三元表达式。这意味着即使达到终止条件,递归也不会结束,因为当a==1 时,要确定fact(0) 的返回类型,编译器必须继续评估对fact 的进一步递归调用,并且会出现无限递归.

    通过声明返回类型,fact(0)a==1时不需要求值,递归可以终止。


    对于两个return语句的情况,相关的标准条款是——

    (来自 N4296)§7.1.6.4/9 [dcl.spec.auto]

    如果具有包含占位符类型的声明返回类型的函数具有多个返回语句,则为每个返回语句推导返回类型。如果每次推导的类型都不相同,则程序是非良构的。

    在您的示例中,在对fact&lt;int&gt;(1) 的调用中,从第一个return 语句推导出的返回类型是int,因此第二个return 语句中fact&lt;int&gt;(0) 的返回类型只能是int 也是。这意味着编译器不需要评估fact&lt;int&gt;(0) 的主体,递归可以终止。

    确实,如果您在第二个return 语句中也强制评估对fact 的调用,例如通过更改第一个示例使T 是一个非类型模板参数

    template <unsigned T>
    constexpr auto fact() {
        if(T==1)
            return 1;
        return T*fact<T-1>();
    }
    

    clang 确实因错误而失败

    致命错误:递归模板实例化超出最大深度 256

    Live demo

    【讨论】:

    • if 版本有两个返回语句。编译器是否使用/标准指定了某种惰性求值,它仅在 a==1 时考虑 if 块中的 return 语句?否则我们会在两个版本中遇到同样的问题。
    • 感谢@Praetorian。我可以自己查(虽然我不是语言律师)。
    • @Praetorian 这与您之前声称的相反。为每个 return 语句推导返回类型,但一旦为 any return 语句推导,返回类型是已知的,可以用作后面 return 语句推导的一部分。您使用template &lt;unsigned T&gt; 的示例失败的原因是因为已知fact&lt;T&gt; 的返回类型没有说明fact&lt;T-1&gt; 的返回类型,但在OP 的代码中调用fact&lt;int&gt; 的是fact&lt;int&gt;
    猜你喜欢
    • 1970-01-01
    • 2013-03-22
    • 1970-01-01
    • 1970-01-01
    • 2023-03-19
    • 2014-11-10
    • 1970-01-01
    • 2020-09-13
    • 2015-07-13
    相关资源
    最近更新 更多