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

constexpr and mutable member and implicit copy-ctor

以下代码在 clang 7+ 中编译,但在 5 和 6(使用 c++17 和 c++14)中不编译。 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;
}

实时代码here.

代码格式错误。作为参考,这里是简化的代码,在所有编译器上都失败:

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

};

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

根据 [expr.const] 7.7,

A variable is usable in constant expressions after its initializing declaration is encountered if it is a constexpr variable,

or ... (3.5) a non-mutable subobject or reference member of any of the above.

这会取消默认复制构造函数的资格。

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

constexpr int f(int i) {
  int j=i;
  ++j;
  return i+j;  // neither is a constant expression
}

rule 是常量表达式中检查的任何内容都必须是常量 其生命周期开始于 during表达式的评估。在您的情况下,createf.x 的生命周期明显开始于常量表达式的求值,即 main 初始化 f。然而,确实没有 Foo 对象可以被不创建该对象的常量表达式复制,无论它是否是 constexpr.

唯一的其他候选问题是复制构造函数是否不是 constexpr,但这些要求非常。唯一相关的是每个(非变体)成员都被初始化,这肯定是令人满意的,并且它至少可以在一个常量表达式中使用,这已经被证明。