C++ 14:使用三元表达式从constexpr中推导出(自动)返回类型

bcu*_*ing 13 c++ ternary-operator constexpr c++14

我正在使用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");
}
Run Code Online (Sandbox Code Playgroud)

用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
Run Code Online (Sandbox Code Playgroud)

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

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

我得到以下编译器错误:

> 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");
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

而这失败了

constexpr auto fact(int a) {
    return a==1 ? 1 : a*fact(a-1);
}
Run Code Online (Sandbox Code Playgroud)

出现以下错误

> 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.
Run Code Online (Sandbox Code Playgroud)

这里发生了什么?

Pra*_*ian 11

评估三元表达式的结果类型是其第二和第三个参数常见类型.

通过让编译器推导出返回类型,您可以强制它评估三元表达式的这两个参数.这意味着即使达到终止条件,递归也不会结束,因为当a==1找出fact(0)编译器的返回类型时,必须继续评估进一步的递归调用fact,然后进行无休止的递归.

通过声明返回类型,fact(0)不需要在何时进行求值a==1,并且递归能够终止.


至于两项return陈述的情况,有关的标准条款是 -

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

如果具有包含占位符类型的声明返回类型的函数具有多个return语句,则会为每个return语句推导出返回类型.如果推断的类型在每次扣除中不相同,则该程序是不正确的.

在您的示例中,在调用中fact<int>(1),从第一个return语句推导出的返回类型是int,因此fact<int>(0)第二个return语句中的返回类型也不能是任何东西int.这意味着编译器不需要计算主体,fact<int>(0)并且递归可以终止.

实际上,如果你fact在第二个return语句中强制评估调用,例如通过更改第一个例子,这T是一个非类型模板参数

template <unsigned T>
constexpr auto fact() {
    if(T==1)
        return 1;
    return T*fact<T-1>();
}
Run Code Online (Sandbox Code Playgroud)

clang确实因错误而失败

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

现场演示