考虑以下示例:
#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的参数时,我希望泛型fibonacci模板的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;
| ^~~~~
为什么会这样呢?是特定于模板化结构体的吗?
4条答案
按热度按时间vxf3dgd41#
我对C++中的惰性求值很熟悉,并且希望在传递参数〈0时,if语句的第二个分支(泛型fibonacci模板)不会被求值。
它不需要求值,但我们这里不处理求值,我们处理的是模板示例化,你使用了
fibonacci<n-1>::value
,这需要示例化完整的对象类型fibonacci<n-1>
,必须检查该类型,看看它是否有成员value
可以在这样的表达式中使用。示例化一个类模板会导致其成员的声明被示例化。静态数据成员的声明包含一个初始化器,因此它也必须被示例化。所以我们需要递归地示例化模板。
简单地命名
fibonacci<n-1>
不会导致它被示例化(向前看声明),如果你想延迟示例化,你必须延迟使用那些需要定义的类型(比如访问成员)。这方面的老的元编程技巧(这与函数式编程非常一致)涉及到帮助器模板。
std::conditional_t
将根据条件选择一个类型。然后,访问该类型(并且仅访问该类型)的::value
。因此,直到实际需要时,才完全示例化任何内容。bybem2ql2#
您可以使用
if constexpr
:pqwbnv8z3#
当
fibonacci
用n
的某个值示例化时,在该示例化中使用的所有表达式也必须被编译。这意味着使用的任何模板也必须被示例化。即使包含模板示例化的表达式从未被求值,这也是必要的。避免在表达式中示例化模板的唯一方法是根本不编译表达式,这样可以避免用不正确的参数示例化模板。
您可以通过使用C++17的
if constexpr
来完成此操作:这是一个demo。
xpcnnkqh4#
Artefacto和Story Teller - Unslander Monica已经提供了正确的答案。
我只是想借助cppinsights进一步解释
为了便于解释,请考虑稍作修改的原始代码:
下面是编译器生成的代码的外观
从上面生成的代码中可以清楚地看到,即使表达式
3 > 0
为真,编译器仍然为n = 2
和n = 1
示例化false
表达式中的模板现在来看一下Artefacto共享的
if constexpr
代码。将模板参数视为
-1
。下面是编译器将如何解释它从上面的代码中可以清楚地看到编译器甚至没有考虑
if constexpr
的else部分,这就是为什么这段代码可以完全正常地工作。