2

我正在尝试实现一些将接收任意位数据(在编译时已知)并将它们的 CRC 计算为 a 的consteval东西,因此我可以使用它来例如用整数键索引此类数据,而无需任何运行时开销。当输入是字符字符串文字时,我让它工作,但是当输入是字符串文字时,我正在努力让它工作wchar_t

我收到一个相当神秘的错误......

error: accessing value of '"T\000e\000s\000t\000\000"' through a 'const char' glvalue in a constant expression

...这似乎是由在 constexpr 上下文中使用 reinterpret_cast 引起的(这显然是不允许的)

我的问题是,有没有办法将任意数据解释为普通的旧字节数组?我不在乎它有多丑陋或缺乏可移植性(只要这一切都发生在编译时)。现在,只需使用数组wchar_t作为输入来解决这个问题就足够了。显然,我可以“只是”为我想单独处理的每种类型重新实现 CRC 计算,但如果可能的话,我宁愿不这样做(事实上,对于比 POD 数组更复杂的任何事情,这将非常棘手)

作为参考,失败的代码如下:

// Details of CRCInternal omitted for brevity
template <size_t len> consteval uint32_t CRC32(const char (&str)[len])
{
    return CRCInternal::crc32<len - 1>(str) ^ 0xFFFFFFFFu;
}

template <size_t len> consteval uint32_t CRC32FromWide(const wchar_t (&filename)[len])
{
    return CRC32(reinterpret_cast<const char(&)[len * sizeof(wchar_t)]>(filename));
}

void main()
{
    CRC32FromWide(L"Test"); // <==== Error
}
4

1 回答 1

3

C++ 对象模型通常是虚构的,是编写代码的程序员和生成二进制可执行文件的编译器之间的协议。对于可执行文件,对象不存在;它只是存储在内存中的位。因此,您可以利用 C++ 具有数十个后门的事实,这些后门可用于有效地假装对象模型不是真实的。其中许多都被声明为表现出未定义的行为,但没有编译器会检查这些违反对象模型的行为并阻止你。你违反了合同,但编译器没有注意,所以你侥幸逃脱。

这不是常量表达式求值的情况。编译后的可执行文件在 CPU 上运行;常量表达式求值编译器中运行。对象模型不必映射到“位”或“内存”或类似的东西;它可以是具有全生命周期跟踪和分析的真实对象模型。

因此,C++ 标准要求,在持续评估期间,如果您执行任何显示 UB 的操作,编译器必须检测到这一点并声明您的程序格式错误。此外, constexpr 代码完全禁止使用最大的后门:reinterpret_cast.

在编译时,对象不是存储中的字节。所以你不能像对待他们一样对待他们。

这一点尤其重要,因为编译器的执行环境和最终二进制文件的执行环境不必相同。如果您正在为某些嵌入式系统进行开发,那么您所针对的 CPU 的字节序可能与编译器在其上执行的 CPU 的字节序不匹配。因此,如果您能够以字节的形式访问任何编译时数据,那么您在编译时会得到与运行时不同的答案。

那很糟。

C ++ 20std::bit_cast存在并且可以提供帮助,但即使这样也不能做所有事情。一个类型仅适用于constexpr bit_cast-ing 如果它是 TriviallyCopyable并且不存储指针(除其他外)。这是因为编译时指针不仅仅是地址;它们是一些复杂的数据类型,必须记住它指向的对象(否则,将无法检测到static_cast它们何时指向一些不相关的类型并尝试通过错误的类型访问对象)。

但是,如果您将类型限制为constexpr bit_cast能够使用的类型,那么您可以bit_cast将它们限制为与其大小相同的数组。

请注意,这constexpr bit_cast并不是最容易实现的事情,因为它必须使源对象数据像在目标 CPU 和环境上执行一样工作,而不是编译器正在其中执行的那个。所以如果目标是大端机器,源是小端,constexpr bit_cast 必须做端转换,而且必须在知道源对象和目的对象的每个组件类型是什么的情况下进行这种转换。

于 2020-08-01T13:39:48.403 回答