我用这段代码在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';
}
编译器资源管理器
来自clang的无法解释的错误消息:
basic_string.h:356:10:注意:常量表达式中不允许向没有活动成员的联合的成员'_M_local_buf'赋值
如果使用libc++(-stdlib=libc++
(,代码没有问题,Clang也会正确编译它。默认情况下,编译器资源管理器会将libstdc++用于Clang(-stdlib=libstdc++
(。两者之间似乎存在兼容性问题。我的猜测是,libstdc++实现constexpr
字符串的方式在技术上是常量表达式中不允许的,但GCC在这方面往往不如Clang严格,因此Clang在实现上失败了,而GCC对此没有问题。
更准确地说,在libstdc++中,std::string
的实现包含一个匿名并集,其中一个成员是名为_M_local_buf
的_CharT
数组。如果求值发生在常量表达式求值中(从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();
以便将联合成员CCD_ 7设置为活动并将其归零
问题是,在此循环之前,没有将并集的成员设置为活动成员,并且通过引用而不是直接通过名称或成员访问表达式来设置成员(子对象(,从技术上讲,标准中没有指定将成员设置为活动成员。因此,从技术上讲,它有未定义的行为,这就是为什么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();
通过将隐式(*this).
添加到_M_local_buf
,这现在满足class.union.general/5中所需的形式,以开始_M_local_buf
阵列成员的生存期,并因此将其设置为活动。