GCC 和 Clang 在 constexpr 构造函数上的不同行为

GCC and Clang different behaviors on constexpr constructor

本文关键字:构造函数 Clang constexpr GCC      更新时间:2023-10-16

对于此结构:

struct Wrapper {
int value;
constexpr explicit Wrapper(int v) noexcept : value(v) {}
Wrapper(const Wrapper& that) noexcept : value(that.value) {}
};

而这个功能:

constexpr Wrapper makeWrapper(int v)
{
return Wrapper(v);
}

以下代码无法针对 Clang(Apple LLVM 版本 7.3.0)编译,但对于 GCC (4.9+) 编译良好,两者都使用-Wall -Wextra -Werror -pedantic-errors

constexpr auto x = makeWrapper(123);

Clang抱怨说:"非constexpr构造函数'Wrapper'不能在常量表达式中使用。哪个编译器是正确的?

虽然从makeWrapper()返回Wrapper时的副本或移动可以省略,但它需要与 C++14 一起存在。现有的复制构造函数是非constexpr的,它的存在抑制了隐式移动构造函数的创建。因此,我认为 clang 是对的:您需要使复制构造函数成为constexpr

请注意,对于 C++17,代码可能会变得正确:有人建议在某些情况下强制使用复制省略:P0135r0。然而,这一变化似乎还没有出现在工作文件中。不过,它可能会在本周登陆(感谢@NicolBolas指出它还没有)。我还没有在邮件中看到更新的论文。

Clang 是正确的。它在 g++ 中工作,因为它会自动省略复制构造函数 (RVO)。如果你通过-fno-elide-constructors.G++也会抱怨。

C++14标准不清楚constexpr对象中的Copy-Elision。

[类.副本/32] ...(此处部分转载)

当满足消除复制/移动操作的条件时......所选构造函数必须可访问,即使调用是 被忽略了。

直到我们知道accessible的定义?我们可以假设 g++ 也是正确的吗?

dcl.constexpr/9

对象声明中使用的 constexpr 说明符声明 对象作为常量。此类对象应具有文字类型,并且应 初始 化。如果它由构造函数调用初始化,则该调用 应为常量表达式 ([expr.const])。否则,或者如果 constexpr 说明符用于引用声明中,每个 出现在其初始值设定项中的全表达式应为常量 表达。

迪特马尔·库尔(Dietmar Kuhl)的回答告诉我们未来会发生什么。

演示:

struct Wrapper {
int value;
constexpr explicit Wrapper(int v) noexcept : value(v) {}
Wrapper(const Wrapper& that) noexcept : value(that.value)  {}
};
constexpr Wrapper makeWrapper(int v)
{
return Wrapper(v);
}
int main()
{
constexpr auto x = makeWrapper(123);
}

编译方式

g++ -std=c++14 -Wall -pedantic -fno-elide-constructors main.cpp && ./a.out

在这里观看直播

两个编译器都是正确的。

constexpr函数和初始化器的规则表明,不能调用非constexpr函数。

复制 elision 的规则表明,是否调用非constexpr复制构造函数是未指定的。

唯一的结论可能是未指定函数和初始化器是否满足constexpr的要求。如果他们这样做,那么编译器必须接受它。如果没有,则编译器必须诊断问题。

相关文章: