具有可变成员的 constexpr 对象

constexpr object with mutable member

I came up with this class:

class Point
{
public:
    int X, Y;
    mutable int Z;

    constexpr Point(int x, int y) :X (x), Y(y), Z(0)
    { }

    constexpr int GetX() const
    {
        // Z++; // Wont compile, but following expression is valid!
        return X+Z;
    }

    int GetY() const
    {
        Z++;
        return Y;
    }

    void FoolConst() const
    {
        Z++;
    }
};

And here is usage:

template<int S>
void foo()
{
    std::cout << S << std::endl;
}

int main()
{   
    constexpr Point pt(10, 20);

    pt.FoolConst();

    char arr[pt.GetX()]; // Both compile, but GCC is using extended `new`

    foo<pt.GetX()>(); // GCC fails, VC compiles

    std::cout << sizeof(arr); // 10 (MSVC), 11 (GCC)
    std::cout << pt.GetX();  // 11 (MSVC), 11(GCC)
}

Questions:

For one compiler constexpr GetX is truly constexpr, but for other it is not if X+Z is involved. If I remove +Z and simply return X GCC is okay.

My question is very basic: If object is constexpr how can it call a non-constexpr method?

海湾合作委员会就在这里。 C++14 标准 [basic.type.qualifier]:

— A const object is an object of type const T or a non-mutable subobject of such an object.

因此,在您的示例中,Z 是非常量,因此不能在常量表达式中使用,就像 GCC 所说:

error: mutable 'Point::Z' is not usable in a constant expression

常量表达式不能访问可变子对象。这是在 [expr.const]/2:

A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (1.9), would evaluate one of the following expressions: [...]

  • an lvalue-to-rvalue conversion (4.1) unless it is applied to [...]
    • a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to a non-mutable sub-object of such an object [...]

因此 GetX 不能在常量表达式中使用,例如作为模板参数 foo<pt.GetX()>().

回答您的具体问题:

  • Why GetX is compiling well with X+Y as return expression (Z is not constexpr).

编译器不需要检查 constexpr 函数(包括成员函数)在定义时是否完全有效,只有在使用。它确实需要检查一些事情,比如不使用 goto [dcl.constexpr]/3,但它不必检查定义访问了哪些对象.这是因为 constexpr 函数是否可以在常量表达式中使用取决于其参数的值。

事实上,因为 GetX 无条件访问 Z,根据 [dcl.constexpr]/5,您的程序严格具有未定义的行为:

For a non-template, non-defaulted constexpr function or a non-template, non-defaulted, non-inheriting constexpr constructor, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression (5.19), the program is ill-formed; no diagnostic required.

"Ill-formed; no diagnostic required" 是另一种表示程序行为未定义的方式。

  • How can I call FoolConst and GetY methods out of constexpr object (pt) ?

那太好了;从该对象的非 constexpr 成员函数的角度来看,声明 constexpr 的对象只是一个 const 对象。

  • The behaviour of GetX in main is different in compilers. MSVC compiles fine with a int as template argument, while GCC (IdeOne) won't compile it.

不幸的是,两个编译器都是正确的;你的程序在 GetX 的定义中有未定义的行为,所以编译器没有一个正确的行为。

答案:

Why GetX is compiling well with X+Y as return expression (Z is not constexpr).

  • 因为整个对象在用 constexpr 声明后是常量。对象的常量性始终由所有成员派生,除非成员被声明为 mutable.

How can I call FoolConst and GetY methods out of constexpr object (pt) ? The behaviour of GetX in main is different in compilers. MSVC compiles fine with a int as template argument, while GCC (IdeOne) won't compile it.

  • 它适用于我,gcc 4.8.3。一切都很好,只要你不在 GetX() 中使用 Z 因为 mutable 字段的使用会破坏 constexpr (OTOH gcc 的行为有点误导因为它应该报告 GetX() 定义中已经存在的错误:要么必须定义它,要么不能定义 constexpr,或者在定义时它不能使用在 [= 之外定义的可变字段或变量43=]).
  • 如果你有一个编译器可以很好地传递 GetX() 的结果作为模板参数,它引用 mutable 字段,它肯定会违反标准,并且这种行为实际上是未定义的,因为它无论 Z 的运行时值发生什么变化,都应该产生相同的结果(因为它是在编译时解决的)。