C++17 静态模板惰性求值

gca*_*lin 20 c++ compiler-errors c++17

考虑以下示例:

#include<iostream>

template<int n>
struct fibonacci {
    static const int value = n < 0 ? 0 : fibonacci<n-1>::value + fibonacci<n-2>::value;
};

template<>
struct fibonacci<1> {
    static const int value = 1;
};

template<>
struct fibonacci<0> {
    static const int value = 0;
};

int main() {
    
    std::cout << fibonacci<-1>::value << std::endl;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我熟悉 C++ 中的惰性求值,并且希望在传递参数 < 0 时不会求值 if 语句的第二个分支泛型斐波那契模板。但是,编译代码仍然会导致该分支的无限循环:

Fibonacci.cpp: In instantiation of ‘const int fibonacci<-900>::value’:
Fibonacci.cpp:5:58:   recursively required from ‘const int fibonacci<-2>::value’
Fibonacci.cpp:5:58:   required from ‘const int fibonacci<-1>::value’
Fibonacci.cpp:20:33:   required from here
Fibonacci.cpp:5:58: fatal error: template instantiation depth exceeds maximum of 900 (use ‘-ftemplate-depth=’ to increase the maximum)
    5 |     static const int value = n < 0 ? 0 : fibonacci<n-1>::value + fibonacci<n-2>::value;
      |                                                          ^~~~~
Run Code Online (Sandbox Code Playgroud)

为什么会这样?它特定于与模板化结构相关的东西吗?

Sto*_*ica 21

我熟悉 C++ 中的惰性求值,并且希望在传递参数 < 0 时不会求值 if 语句的第二个分支泛型斐波那契模板。

它不需要评估。但我们在这里不处理评估。我们正在处理模板实例化。您使用了fibonacci<n-1>::value,这需要fibonacci<n-1>实例化完整的对象类型。必须检查该类型,以查看它是否具有value可以在此类表达式中使用的成员。

实例化类模板会导致其成员的声明被实例化。静态数据成员的声明包含一个初始化程序,因此也必须对其进行实例化。所以我们需要递归地实例化模板。

简单的命名fibonacci<n-1>不会导致它被实例化(想想前向声明)。如果要延迟实例化,则必须以需要它们定义的方式(例如访问成员)延迟使用这些类型。

旧的元编程技巧(非常符合函数式编程)涉及辅助模板。

template<class L, class R>
struct add {
    static constexpr auto value = L::value + R::value;
};

template<int n>
struct fibonacci {
    static const int value = std::conditional_t<(n < 0), fibonacci<0>, add<fibonacci<n-1>, fibonacci<n-2>>>::value;
};
Run Code Online (Sandbox Code Playgroud)

std::conditional_t将根据条件选择类型。然后,访问::value该类型(并且仅该类型)的 。所以在真正需要之前,没有什么是完全实例化的。


Art*_*cto 13

您可以使用if constexpr

template<int n>
struct fibonacci {
    static const int value = []() {
        if constexpr (n < 0) {
            return 0;
        } else {
            return fibonacci<n-1>::value + fibonacci<n-2>::value;
        }
    }();
};
Run Code Online (Sandbox Code Playgroud)


cig*_*ien 5

fibonacci使用 的某个值实例化时n,在此实例化中使用的所有表达式也必须被编译。这意味着使用的任何模板也必须被实例化。即使从未评估包含模板实例的表达式,这也是必要的。

避免在表达式中实例化模板的唯一方法是根本不编译该表达式。这允许您避免使用不正确的参数实例化模板。

您可以通过if constexpr从 C++17使用来做到这一点:

template<int n>
struct fibonacci {
    static const int value = [] {
      if constexpr (n < 0) return 0;
       else return fibonacci<n-1>::value + fibonacci<n-2>::value;
    }();
};
Run Code Online (Sandbox Code Playgroud)

这是一个演示