constexpr、可变成员和隐式复制构造函数

4
下面的代码在 clang 7+ 中编译通过,但在 c++17 和 c++14 的 clang 5 和 6 中不编译通过。 对于 clang 5 和 6,问题似乎是隐式复制构造函数从可变成员 x 中读取了值。
请问是否整个构造是符合标准的(c++17),或者程序是非法的?或者隐式复制构造函数有关于标准的变化,这些变化在较早版本的clang中可能没有实现?
struct Foo {

    int a;
    mutable int x{};

    constexpr Foo() : a(0) {}

    //constexpr Foo(const Foo& other) : a(other.a) {} // <- with this line it works on Clang 5 & 6, too
};

struct FooFactory {

    static constexpr auto create() {
        auto f = Foo{};       
        return f;
    }
};

int main() {

    constexpr Foo f = FooFactory::create();
    ++f.x;
}

可以在这里实时查看代码

2个回答

4

这是一个规范的 C++17 程序。当然,constexpr 函数可以读取(和写入)非常量变量:

constexpr int f(int i) {
  int j=i;
  ++j;
  return i+j;  // neither is a constant expression
}
规则是:在常量表达式中检查的任何内容必须是一个常量,或者在表达式评估期间其生存期开始。在您的情况下,createf.x 的生存期明显始于 mainf初始化常量表达式的评估过程中。然而,的确没有任何 Foo 对象可以通过不创建该对象的常量表达式来复制,无论它是否为 constexpr
唯一可能存在的问题是复制构造函数不是 constexpr,但这些要求非常。唯一相关的要求是每个(非变体)成员都被初始化,这肯定已经满足,并且它至少可以在一个常量表达式中使用,这已经得到证明。

非常有趣,谢谢。我提到了从非常量成员读取,因为这是clang 6在this-is-not-a-constant-expression错误后打印的一个注释。 - florestan

0

代码格式不正确。以下是简化的代码参考,该代码在所有编译器上都无法通过:

struct Foo {
    int a;
    mutable int x{};
    constexpr Foo() : a(0) {}

};

int main() {
    constexpr Foo f;
    constexpr Foo f1 = f;
}

根据 [expr.const] 7.7,
变量在其初始化声明被遇到后,如果它是 constexpr 变量,或者是上述任何一个的非可变子对象或引用成员,则可以在常量表达式中使用。
这使默认复制构造函数不符合条件。

Clang 可能不会抱怨 OP 的代码,因为 NRVO 起作用了。添加 constexpr Foo f1 = f; 确实会导致错误。 - HolyBlackCat
不错,这似乎就是原因!谢谢你们! - florestan
2
这种简化似乎不等价。constexpr函数并不要求只使用常量表达式。在我看来,原始的工厂函数调用Foo的默认构造函数来创建f,然后使用复制构造函数初始化其返回对象。这两者都不需要f在常量表达式中可用。初始化constexpr Foo f = FooFactory::create()没有直接调用任何构造函数。f是通过调用的评估进行初始化的。因此,f初始化的完整表达式只是对constexpr函数的调用,这是允许的。 - HTNW
这是 [expr.const]/3,而不是7.7,并且是C++20草案的一部分,而不是17,该项目符号不对应于变量,而是对象或引用。 - Davis Herring

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接