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:
- Why
GetX
is compiling well with X+Z
as return expression (Z is not constexpr).
- 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.
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?
Answers:
Why GetX
is compiling well with X+Y
as return expression (Z
is not constexpr
).
- Because whole object is constant after having been declared with
constexpr
. Const-ness of the object is always derived by all members, except when the member is declared 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.
- It works for me, with gcc 4.8.3. Everything is fine, as long as you don't use
Z
in GetX()
because the use of mutable
field destroys constexpr
(OTOH the behavior of gcc is a little bit misleading because it should report the error already in the definition of GetX()
: either that it must be defined or otherwise can't be constexpr
, or when defined it cannot use mutable fields or variables defined outside the class).
- If you have a compiler that compiles fine passing the result of
GetX()
as template argument, which refers to a mutable
field, it surely breaks the standard and this behavior is actually undefined because it should produce the same result (due to the fact that it's resolved in compile time) no matter what happened to the runtime value of Z.
A constant expression cannot access a mutable sub-object. This is in [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 [...]
So GetX
cannot be used within a constant expression, e.g. as a template parameter foo<pt.GetX()>()
.
In answer to your specific questions:
- Why GetX is compiling well with X+Y as return expression (Z is not constexpr).
A compiler is not required to check that constexpr functions (incl. member functions) are fully valid when they are defined, only when they are used. It does have to check a few things, like not using goto
[dcl.constexpr]/3, but it doesn't have to check which objects the definition accesses. This is because whether the constexpr function can be used within a constant expression can depend on the values of its arguments.
In fact, because GetX
unconditionally accesses Z
, your program strictly has undefined behavior per [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" is another way of saying that the behavior of your program is undefined.
- How can I call FoolConst and GetY methods out of constexpr object (pt) ?
That's absolutely fine; an object declared constexpr
is just a const
object from the point of view of non-constexpr
member functions of that object.
- 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.
Unfortunately, both compilers are correct; your program has undefined behavior in the definition of GetX
, so there is no single correct behavior for the compiler.
GCC is right here. C++14 standard [basic.type.qualifier]:
— A const object is an object of type const T or a non-mutable subobject of such an object.
So, in your example Z is non-const and therefore cannot be used in a constant expression, just like GCC said:
error: mutable 'Point::Z' is not usable in a constant expression