为什么 GCC 错误地检测到移位计数溢出?

Why is GCC wrongly detecting shift count overflow?

我正在尝试从常量字符串中创建最多 8 个字符的编译时标签集。比如“abc”应该变成0x6162630000000000,“”应该变成0等等

为此,我想我应该利用对数组的引用不会衰减到指针这一事实,我可以用这个捕获 const 数组的 N(大小):

template <size_t N>
inline constexpr uint64_t make_tag(char const (&arr)[N]) {
    if (N == 1) {
        return 0;
    }

    uint64_t result = 0;

    for (size_t i = 0; (i < (N-1)) && (i<8); i++) {
        result = (result << 8) | arr[i];
    }

    if ((N-1) < 8) {
        result = result << (8 * (8 - (N-1)));
    }

    return result;
}

N 被正确捕获,“”为 1,其余字符串为 len+1。

这是让我感到困惑的事情:当做类似

的事情时
static constexpr uint64_t a0=make_tag("");

GCC 11.2 告诉我这个:

<source>: In instantiation of 'constexpr uint64_t make_tag(const char (&)[N]) [with long unsigned int N = 1; uint64_t = long unsigned int]':
<source>:24:43:   required from here
<source>:17:33: error: left shift count >= width of type [-Werror=shift-count-overflow]
   17 |                 result = result << (8 * (8 - (N-1)));
      |                          ~~~~~~~^~~~~~~~~~~~~~~~~~~~
cc1plus: all warnings being treated as errors

clang 不会那样做。

为方便起见,这里是 compiler explorer snippet:

编辑:if (N == 1) {...} else {...} 按照@Useless 的建议做了技巧

Why is GCC wrongly detecting shift count overflow?

不是。

N=1时,代码

    if ((N-1) < 8) {
        result = result << (8 * (8 - (N-1)));
    }

产生 64 位移位。

如果您不想为 N=1 编译该代码,请明确说明:

    if (N > 1 && (N-1) < 8) {
        result = result << (8 * (8 - (N-1)));
    }

将所有内容都放在初始检查 else 分支中 if (N ==1) { return 0; } else { ... 也可以。

目前还不清楚为什么 GCC 没有传播早期 return 隐含的 N 约束,但我怀疑它根本不需要。

注意 if constexpr 周围的标准语言说:

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.

if constexpr 语句之后静态丢弃任何内容,即使采用的分支有一个早期的 return。当然可以优化掉,但首先还是要能编译。