clang constexpr 在 lambda、gcc 和 msvc 中编译错误,正常。叮当错误?

dou*_*oug 4 c++ lambda c++20

我在使用此代码的 clang 中遇到了编译错误。我没有发现它有任何问题,它可以在 msvc 和 gcc 中工作。我错过了什么还是它是一个错误?

#include <iostream>
#include <string>
#include <type_traits>

constexpr bool do_tests()
{
    // prints error message at runtime if test is false
    auto verify = [](bool test, std::string message = "error") {
        if (!std::is_constant_evaluated() && !test)
            std::cout << message << '\n';
        return test;
    };
    return verify(1 == 1, "test");
};


int main()
{
    constexpr bool b = do_tests();
    std::cout << b << '\n';
}
Run Code Online (Sandbox Code Playgroud)

编译器浏览器

来自 clang 的莫名其妙的错误消息:

basic_string.h:356:10:注意:在常量表达式中不允许对没有活动成员的联合体的成员“_M_local_buf”进行赋值

use*_*522 7

代码没有问题,如果使用 libc++ ( -stdlib=libc++),Clang 也能正确编译它。编译器资源管理器默认使用 Clang ( -stdlib=libstdc++) 的 libstdc++。两者之间似乎存在兼容性问题。我的猜测是 libstdc++ 正在constexpr以常量表达式中技术上不允许的方式实现字符串,但 GCC 在这方面往往不如 Clang 严格,因此 Clang 实现失败,而 GCC 没有问题用它。


更准确地说,在 libstdc++ 中, 的实现std::string包含一个匿名联合,其中一个成员是_CharT名为 的数组_M_local_buf。如果评估发生在常量表达式评估中,则 libstdc++ 会执行以下操作(来自https://github.com/gcc-mirror/gcc/blob/releases/gcc-12.2.0/libstdc++-v3/include/bits/basic_string .h#L356 )

for (_CharT& __c : _M_local_buf)
    __c = _CharT();
Run Code Online (Sandbox Code Playgroud)

为了将联合成员设置_M_local_buf为活动状态并将其归零。

问题是,在此循环之前,联合体的任何成员都没有设置为活动状态,并且通过引用设置成员(子对象),而不是直接通过名称或通过成员访问表达式,技术上没有在标准中指定设置成员(子对象)。会员活跃。因此,从技术上讲,它具有未定义的行为,这就是为什么 Clang(正确地)不想接受它作为常量表达式。如果该成员预先设置为活动状态,例如使用额外的线路,_M_local_buf[0] = _CharT();那就没问题了。

当然,_M_local_buf[0]直接设置可以,但通过引用设置则__c不行,这似乎有点愚蠢,但这可能有充分的理由。通常不能保证引用引用编译时已知的特定对象,因此它可能会使编译器更难识别表达式是否需要限制其周围的某些优化,如果活动成员已更改。


这里有一个与非常相似的问题相关的 libstdc++ 错误报告,但应该已经修复了一段时间。

提交https://github.com/gcc-mirror/gcc/commit/98a0d72a610a87e8e383d366e50253ddcc9a51dd似乎(重新)引入了您在这里看到的特定问题。在提交之前,成员已正确设置为活动状态。可能值得提出一个关于此问题的问题,尽管如上所述,Clang 在这里非常迂腐,并且正如链接的错误报告也所说,标准可能应该更改以允许这样做。


更新:

GCC 版本 13.1 和 12.3 中有一个 libstdc++ 提交,它确实解决了使用 Clang 编译时的问题。请参阅提交https://github.com/gcc-mirror/gcc/commit/52672be7d328df50f9a05ce3ab44ebcae50fee1b

_M_local_buf[__i] = _CharT();
Run Code Online (Sandbox Code Playgroud)

(*this).添加隐式后_M_local_buf,现在满足 class.union.general/5 中启动数组成员的生命周期所需的形式_M_local_buf,因此将其设置为活动状态。