【问题标题】:Ternary operator evaluation rules in templates different?模板中的三元运算符评估规则不同?
【发布时间】:2019-08-27 11:48:22
【问题描述】:

鉴于这个无辜的 sn-p:

#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 on Godbolt,而我们也使用 Clang,因为这只是一次单击。

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

所以我有点不安。好像我没有正确理解条件运算符的规则?对于计算方式不同的模板或模板变量,是否有任何特殊例外?

【问题讨论】:

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


    【解决方案1】:

    当你使用if constexpr那么这部分代码

    else return bit<n> - 1;
    

    n 等于 64 时不实例化。

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

    2 如果 if 语句的形式为 if constexpr,则 条件应是上下文转换的常量表达式 类型布尔(8.6);这种形式称为 constexpr if 语句。如果 转换条件的值为假,第一个子语句是 丢弃的语句,否则第二个子语句(如果存在)是 被丢弃的语句。在封闭的实例化期间 模板化实体(第 17 条),如果条件不是 实例化后的值相关,丢弃的子语句 (如果有的话)没有被实例化。

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

    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()
    {
    }
    

    【讨论】:

    • 它与constexpr if 一起工作的事实一点也不让我感到惊讶。它不适用于 ?: 的事实确实如此。毫无疑问,整个表达式肯定是实例化的。所以它必须在语法上是正确的,等等(例如,它不能包含未定义的类型)。但我对[expr.cond] 1 的理解是(1ull&lt;&lt;64) 在任何情况下都不会被评估。这意味着不会有错误(另外,令我惊讶的是,clang 认为模板参数 not-a-constant-expression 发生了变化)。这么少看似简单和微不足道的代码却带来了这么多麻烦。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-03
    • 2016-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-04
    相关资源
    最近更新 更多