C++11 constexpr 函数编译器错误与三元条件运算符 (? :)

标签 c++ c++11 recursion ternary-operator constexpr

这段代码有什么问题?

#include <iostream>

template<unsigned int N, unsigned int P=0>
constexpr unsigned int Log2() {
    return (N <= 1) ? P : Log2<N/2,P+1>();
}

int main()
{
    std::cout << "Log2(8) = " << Log2<8>() << std::endl;
    return 0;
}

使用 gcc 4.6.3 版 (Ubuntu/Linaro 4.6.3-1ubuntu5) 编译时,出现以下错误:

log2.cpp: In function ‘constexpr unsigned int Log2() [with unsigned int N = 0u, unsigned int P = 1023u]’:
log2.cpp:5:38: error: template instantiation depth exceeds maximum of 1024 (use -ftemplate-depth= to increase the maximum) instantiating ‘constexpr unsigned int Log2() [with unsigned int N = 0u, unsigned int P = 1024u]’
log2.cpp:5:38:   recursively instantiated from ‘constexpr unsigned int Log2() [with unsigned int N = 4u, unsigned int P = 1u]’
log2.cpp:5:38:   instantiated from ‘constexpr unsigned int Log2() [with unsigned int N = 8u, unsigned int P = 0u]’
log2.cpp:10:37:   instantiated from here

最佳答案

Constexpr 不是那样工作的。

简而言之,constexpr 函数也必须作为运行时函数可用。想象一下,您从函数中删除了 constexpr。然后想想为什么它不可能工作。

原因是编译器必须完全实例化函数体;它不能根据 ?: 中的条件来决定不要实例化一侧。所以它总是要实例化递归调用,导致无限递归。

无论如何,您使用的 constexpr 是错误的。当 constexpr 打算替换它时,您正在使用旧的模板元编程计算技术(将内容作为模板参数传递)。只需使用普通参数即可。

constexpr unsigned Log2(unsigned n, unsigned p = 0) {
    return (n <= 1) ? p : Log2(n / 2, p + 1);
}

std::cout << "Log2(8) = " << Log2(8) << std::endl;

编辑:我将尝试详细说明其工作原理。

当编译器遇到你的代码时,它会解析模板函数并以模板形式存储。 (编译器之间的工作方式不同。)到目前为止,一切都很好。

接下来,在 main ,编译器看到调用 Log2<8>() .它看到它必须实例化模板,所以它继续并确实这样做:它实例化 Log2<8, 0> .函数模板的主体是这样的:

return (N <= 1) ? P : Log2<N/2,P+1>();

好的,编译器看到了这个,但它不会尝试评估它。为什么会这样?它目前正在实例化一个模板,而不是计算一个值。它只是替换提供的值:

return (8 <= 1) ? 0 : Log2<8/2,0+1>();

咦,这里又多了一个模板实例化。它在条件表达式中或者左侧可以知道都没有关系。模板实例化必须完成。所以它继续计算新实例化的值,然后实例化 Log2<4, 1> :

return (4 <= 1) ? 1 : Log2<4/2,1+1>();

然后游戏又开始了。那里有一个模板实例化,它是 Log2<2, 2> :

return (2 <= 1) ? 2 : Log2<2/2,2+1>();

再一次,Log2<1,3>() :

return (1 <= 1) ? 3 : Log2<1/2,3+1>();

我有没有提到编译器不关心这些东西的语义?这只是另一个要实例化的模板:Log2<0,4> :

return (0 <= 1) ? 4 : Log2<0/2,4+1>();

然后 Log2<0,5> :

return (0 <= 1) ? 5 : Log2<0/2,5+1>();

等等,等等。在某个时候,编译器意识到它永远不会停止,并放弃了。但它绝不会说:“等等,那个三元运算符的条件为假,我不需要实例化右侧。”那是因为 C++ 标准不允许这样做。函数体必须完全实例化。

现在看看我的解决方案。没有模板。只有一个功能。编译器看到它然后说,“嘿,这是一个函数。太棒了,让我在这里调用那个函数。”然后在某个时候(可能是立即,也可能是很久以后,取决于编译器),它可能(但在这种情况下不是强制的)说,“嘿,等等,这个函数是 constexpr我知道参数值,让我评估一下。”现在它继续评估 Log2(8, 0) .记住 body :

return (n <= 1) ? p : Log2(n / 2, p + 1);

“好的”,编译器说,“我只想知道这个函数返回什么。让我们看看,8 <= 1 是假的,所以看右边。Log2(4, 1),嗯?让我看看那个。好的,4 <= 1 也是错误的,所以它必须是 Log2(2, 2)。那是什么,2 <= 1?也是错误的,所以它是 Log2(1, 3)。嘿,1 <= 1 是正确的,所以让我把 3 和返回它。一直到调用堆栈。”

所以它得出了答案 3。它不会进入无休止的递归,因为它是在充分了解值和语义的情况下评估函数,而不仅仅是愚蠢地构建 AST。

希望对您有所帮助。

关于C++11 constexpr 函数编译器错误与三元条件运算符 (? :),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18231591/

相关文章:

c++ - 使用 libc 的 SIGSEGV 回溯是重复条目

c++ - 如何将 .pgm 纹理文件加载到 openGL

c++ - 获取具有相等值的 map 元素范围

haskell - 为什么haskell中的递归列表这么慢?

java - C/C++ 中的什么功能最适合 Java 中的集合 Disruptor?

c++ - 标准库标识符与用户标识符冲突

递归链接对象的算法

c++ - 寻找更好的回溯系统

c++11 - 对 '__gthrw___pthread_key_create(unsigned int*, void (*)(void*)) 的 undefined reference

c++ - 如何在 C++11 中防止线程饥饿