c++ - 模板中的三元运算符评估规则不同?

标签 c++ templates c++17 ternary-operator

鉴于这个无辜的片段:

#include <cstdint>

template <unsigned int n> constexpr uint64_t bit  = (1ull << n);
template <unsigned int n> constexpr uint64_t mask = (n == 64) ? ~0ull : bit<n> - 1;

namespace this_works_fine
{
    template <unsigned int n> constexpr uint64_t bit  = (1ull << n);
    template <unsigned int n> constexpr uint64_t mask = []() constexpr {  if constexpr (n == 64) return ~0ull; else return bit<n> - 1; }();
}

int main()
{
  auto a = mask<64>;
  (void)a;
}

...我希望“正常工作,零错误,零警告”。它非常清晰和简单,没有太多做错事的余地。唯一要注意的是,移动超过整数宽度的是 UB(发生在 N == 64 上),但这已被明确处理。对于大于 64 的值,它可能会产生警告/错误,但这没关系,不需要显式错误检查。

条件运算符仅根据第一个操作数的评估评估第二个第三个操作数。因此,只要代码在原则上语法上都是正确的,我们就可以开始了。

现在,GCC (9.1.0) 告诉我以下内容:

g++.exe -Wall -fexceptions -O2 --std=c++17 -c main.cpp -o obj\main.o
g++.exe  -o lib\gcc-bug.exe obj\main.o -s 
main.cpp: In instantiation of 'constexpr const uint64_t bit<64>':
main.cpp:4:73:   required from 'constexpr const uint64_t mask<64>'
main.cpp:14:12:   required from here
main.cpp:3:59: error: right operand of shift expression '(1 << 64)' is >= than the precision of the left operand [-fpermissive]
    3 | template <unsigned int n> constexpr uint64_t bit  = (1ull << n);
      |                                                     ~~~~~~^~~~~

if constexpr() 重写了完全相同的内容相反编译(当然,工作)没有任何问题。没有错误,没有警告。没有惊喜。为什么它不起作用!

当我准备向 GCC 提交一个“明显损坏”的错误报告时,我突然想到我可能首先检查版本 9.2(MinGW 尚不可用)以及 trunk在 Godbolt 上,同时我们也在使用 Clang,因为这只是再点击一次。

毫不奇怪,其他 GCC 版本会产生相同的错误,但令我惊讶的是,Clang 也不会编译它。它声称 (1ull << n)不是常量表达式。这是另一个故事,但同样令人惊叹。

所以我有点不安。好像我没有正确理解条件运算符的规则?模板或模板变量是否有任何特殊的异常(exception)情况,它们的计算方式不同?

最佳答案

当您使用if constexpr 时,这部分代码

else return bit<n> - 1;

当n等于64时不实例化

来自 C++ 标准(9.4.1 if 语句)

2 If the if statement is of the form if constexpr, the value of the condition shall be a contextually converted constant expression of type bool (8.6); this form is called a constexpr if statement. If the value of the converted condition is false, the first substatement is a discarded statement, otherwise the second substatement, if present, is a discarded statement. During the instantiation of an enclosing templated entity (Clause 17), if the condition is not value-dependent after its instantiation, the discarded substatement (if any) is not instantiated.

与此代码相反的所有部分代码

template <unsigned int n> constexpr uint64_t mask = (n == 64) ? ~0ull : bit<n> - 1;

被实例化。所以编译器会报错。

只需尝试以下语义等效的代码,您将得到相同的错误。

#include <cstdint>

template <unsigned int n> constexpr uint64_t bit  = (1ull << n);

template uint64_t bit<64>;

int main()
{
}

关于c++ - 模板中的三元运算符评估规则不同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57674036/

相关文章:

c++ - 需要明确类模板中友元运算符+的自动返回类型推导

c++ - 构造函数重载以接受任何函数

c++ - 为什么构造函数没有被调用

c++ - 在 OSx : ld: symbol(s) not found for architecture x86_64 上使用 CMake 构建 C++ 项目

c++ - 生成随机数的函数,不是那么随机

c++ - 为什么临时成员函数不绑定(bind)到正确的类型?

c++ - 小行星游戏 C++ 惯性

C++ - 在树作业 : error: expected constructor, 析构函数上编译错误,或在 ‘*’ 标记之前进行类型转换

C++ 模板特化 - 非类型模板参数 '__formal 的非法类型

c++ - 与 get、tie 和其他元组操作一起使用的元组包装器