constexpr对象与可变成员

时间:2016-08-09 12:59:26

标签: c++ c++11 c++14 language-lawyer constexpr

我想出了这堂课:

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++;
    }
};

这是用法:

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)
}

问题:

  • 为什么GetXX+Z编译良好作为返回表达式(Z不是constexpr)。
  • 如何从FoolConst对象(GetY)中调用constexprpt方法?
  • GetXmain的行为在编译器中有所不同。 MSVC使用int作为模板参数进行编译,而GCC(IdeOne)不会编译它。

对于一个编译器constexpr GetX是真正的constexpr,但对于其他编译器,如果涉及X+Z则不是。如果我删除+Z,只需return X GCC就可以了。

我的问题非常基础:如果对象是constexpr,它怎么能调用非constexpr方法?

3 个答案:

答案 0 :(得分:4)

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

  

条件表达式e是一个核心常量表达式,除非e的评估遵循规则   抽象机器(1.9),将评估以下表达式之一:[...]

     
      
  • 左值 - 右值转换(4.1),除非它适用于[...]      
        
    • 一个非易失性glvalue,它引用用constexpr定义的非易失性对象,或者引用   到这样一个对象的不可变的子对象[...]
    •   
  •   

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

回答您的具体问题:

  
      
  • 为什么GetX使用X + Y作为返回表达式编译得很好(Z不是constexpr)。
  •   

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

事实上,由于GetX无条件访问Z,因此您的程序严格按照 [dcl.constexpr] / 5进行了未定义的行为:

  

对于非模板,非默认的constexpr函数或非模板,非默认的,非继承的   constexpr构造函数,如果不存在参数值,则调用函数或构造函数   可能是核心常量表达式的评估子表达式(5.19),该程序是不正确的;没有   需要诊断。

&#34;第III形成的;无需诊断&#34;另一种说法是你的程序行为未定义。

  
      
  • 如何从constexpr对象(pt)中调用FoolConst和GetY方法?
  •   

那绝对没问题;从该对象的非constexpr成员函数的角度来看,声明为const的对象只是一个constexpr对象。

  
      
  • GetX在main中的行为在编译器中是不同的。 MSVC使用int作为模板参数进行编译,而GCC(IdeOne)不会编译它。
  •   

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

答案 1 :(得分:2)

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

  

- const对象是const T类型的对象或这种对象的非可变子对象。

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

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

答案 2 :(得分:1)

数目:

  

为什么GetXX+Y汇编得很好,因为返回表达式(Z不是constexpr)。

  • 因为在用constexpr声明后整个对象是常量。对象的约束总是由​​所有成员派生,除非声明成员mutable
  

如何从FoolConst对象(GetY)中调用constexprpt方法?   main中GetX的行为在编译器中是不同的。 MSVC使用int作为模板参数进行编译,而GCC(IdeOne)不会编译它。

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