C++17 静态模板惰性求值
C++17 static template lazy evaluation
考虑以下示例:
#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;
}
我熟悉 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;
| ^~~~~
为什么会这样?它是否特定于与模板化结构相关的内容?
您可以使用 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;
}
}();
};
I am familiar with lazy evaluation in C++, and would expect that the second branch of the if statement the generic fibonacci template would not be evaluated, when an argument < 0 is passed.
不需要评价。但我们在这里不处理评估。我们正在处理模板实例化。您使用了 fibonacci<n-1>::value
,这需要实例化完整的对象类型 fibonacci<n-1>
。必须检查类型,看它是否有可以在这样的表达式中使用的成员 value
。
实例化 class 模板会导致其成员的声明被实例化。静态数据成员的声明包含一个初始化程序,因此也必须对其进行实例化。所以我们遇到了递归实例化模板的需求。
简单地命名 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;
};
std::conditional_t
会根据条件选择类型。然后,访问该类型(且仅该类型)的::value
。因此,在实际需要之前,没有什么是完全实例化的。
当 fibonacci
被实例化为 n
的某个值时,该实例化中使用的所有表达式也必须被编译。这意味着任何使用的模板也必须被实例化。即使从未评估包含模板实例化的表达式,这也是必要的。
避免在表达式中实例化模板的唯一方法是根本不编译表达式。这使您可以避免使用不正确的参数实例化模板。
您可以使用 C++17 中的 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;
}();
};
这里是 demo。
Artefacto and Story Teller - Unslander Monica 已经给出了正确答案。
我只是想在 cppinsights
的帮助下进一步解释
为了便于解释,请考虑稍作修改的原始代码:
...
template<int n>
struct fibonacci {
static const int value = n > 0 ? 0 : fibonacci<n-1>::value + fibonacci<n-2>::value;
};
...
int main() {
std::cout << fibonacci<3>::value << std::endl; // modified parameter from -1 to 3 to avoid compilation failure by recursive instantiation
return 0;
}
编译器生成的结果如下所示
...
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct fibonacci<3>
{
static const int value = 3 > 0 ? 0 : fibonacci<2>::value + fibonacci<1>::value;
};
#endif
/* First instantiated from: insights.cpp:5 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct fibonacci<2>
{
static const int value = 2 > 0 ? 0 : fibonacci<1>::value + fibonacci<0>::value;
};
#endif
...
int main()
{
std::cout.operator<<(fibonacci<2>::value).operator<<(std::endl);
return 0;
}
从上面生成的代码可以清楚地看出,即使表达式 3 > 0
为真,编译器仍然会在 false
表达式中为 n = 2
和 n = 1
[= 实例化模板24=]
现在看一下Artefacto分享的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;
}
}();
};
正在将模板参数视为 -1
。这是编译器将如何解释它
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct fibonacci<-1>
{
class __lambda_5_30
{
public:
inline /*constexpr */ int operator()() const
{
if constexpr(-1 < 0) {
return 0;
}
}
using retType_5_30 = int (*)();
inline /*constexpr */ operator retType_5_30 () const noexcept
{
return __invoke;
};
private:
static inline int __invoke()
{
if constexpr(-1 < 0) {
return 0;
}
}
} __lambda_5_30{};
static const int value = __lambda_5_30.operator()();
class __anon_5_30;
};
#endif
从上面的代码可以清楚地看出,编译器甚至没有考虑 if constexpr
的 else 部分。这就是为什么这段代码可以完全正常工作的原因。
考虑以下示例:
#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;
}
我熟悉 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;
| ^~~~~
为什么会这样?它是否特定于与模板化结构相关的内容?
您可以使用 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;
}
}();
};
I am familiar with lazy evaluation in C++, and would expect that the second branch of the if statement the generic fibonacci template would not be evaluated, when an argument < 0 is passed.
不需要评价。但我们在这里不处理评估。我们正在处理模板实例化。您使用了 fibonacci<n-1>::value
,这需要实例化完整的对象类型 fibonacci<n-1>
。必须检查类型,看它是否有可以在这样的表达式中使用的成员 value
。
实例化 class 模板会导致其成员的声明被实例化。静态数据成员的声明包含一个初始化程序,因此也必须对其进行实例化。所以我们遇到了递归实例化模板的需求。
简单地命名 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;
};
std::conditional_t
会根据条件选择类型。然后,访问该类型(且仅该类型)的::value
。因此,在实际需要之前,没有什么是完全实例化的。
当 fibonacci
被实例化为 n
的某个值时,该实例化中使用的所有表达式也必须被编译。这意味着任何使用的模板也必须被实例化。即使从未评估包含模板实例化的表达式,这也是必要的。
避免在表达式中实例化模板的唯一方法是根本不编译表达式。这使您可以避免使用不正确的参数实例化模板。
您可以使用 C++17 中的 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;
}();
};
这里是 demo。
Artefacto and Story Teller - Unslander Monica 已经给出了正确答案。
我只是想在 cppinsights
的帮助下进一步解释为了便于解释,请考虑稍作修改的原始代码:
...
template<int n>
struct fibonacci {
static const int value = n > 0 ? 0 : fibonacci<n-1>::value + fibonacci<n-2>::value;
};
...
int main() {
std::cout << fibonacci<3>::value << std::endl; // modified parameter from -1 to 3 to avoid compilation failure by recursive instantiation
return 0;
}
编译器生成的结果如下所示
...
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct fibonacci<3>
{
static const int value = 3 > 0 ? 0 : fibonacci<2>::value + fibonacci<1>::value;
};
#endif
/* First instantiated from: insights.cpp:5 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct fibonacci<2>
{
static const int value = 2 > 0 ? 0 : fibonacci<1>::value + fibonacci<0>::value;
};
#endif
...
int main()
{
std::cout.operator<<(fibonacci<2>::value).operator<<(std::endl);
return 0;
}
从上面生成的代码可以清楚地看出,即使表达式 3 > 0
为真,编译器仍然会在 false
表达式中为 n = 2
和 n = 1
[= 实例化模板24=]
现在看一下Artefacto分享的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;
}
}();
};
正在将模板参数视为 -1
。这是编译器将如何解释它
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct fibonacci<-1>
{
class __lambda_5_30
{
public:
inline /*constexpr */ int operator()() const
{
if constexpr(-1 < 0) {
return 0;
}
}
using retType_5_30 = int (*)();
inline /*constexpr */ operator retType_5_30 () const noexcept
{
return __invoke;
};
private:
static inline int __invoke()
{
if constexpr(-1 < 0) {
return 0;
}
}
} __lambda_5_30{};
static const int value = __lambda_5_30.operator()();
class __anon_5_30;
};
#endif
从上面的代码可以清楚地看出,编译器甚至没有考虑 if constexpr
的 else 部分。这就是为什么这段代码可以完全正常工作的原因。