Example:
int main(int argc, char**)
{
constexpr int a = argc * 0;
(void)a;
constexpr int b = argc - argc;
(void)b;
return 0;
}
argc
is not a constant expression, but the compiler is still able to compute the results of a
and b
in compile time (i.e. 0
) in both cases.
g++ accepts the code above, while clang and MSVC14 reject it.
Does the standard allows the compiler being as smart as g++ with regard to constexpr
?
A constexpr variable must be initialized at compile time. All constexpr variables are const . A variable can be declared with constexpr , when it has a literal type and is initialized. If the initialization is performed by a constructor, the constructor must be declared as constexpr .
The primary usage of constexpr is to declare intent. If an entity isn't marked as constexpr - it was never intended to be used in a constant-expression; and even if it is, we rely on the compiler to diagnose such context (because it disregards our intent).
Even though try blocks and inline assembly are allowed in constexpr functions, throwing exceptions or executing the assembly is still disallowed in a constant expression.
Both const and constexpr mean that their values can't be changed after their initialization. So for example: const int x1=10; constexpr int x2=10; x1=20; // ERROR. Variable 'x1' can't be changed.
Neither argc * 0
nor argc - argc
are constant expressions, the lvalue-to-rvalue conversion is allowed in certain cases none of them apply here. If we look at the draft C++11 standard section 5.19
[expr.const] it lays out the exceptions. It says:
A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression [...]
and has several bullets including the following on lvalue-to-rvalue conversion:
an lvalue-to-rvalue conversion (4.1) unless it is applied to
a glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression, or
a glvalue of literal type that refers to a non-volatile object defined with constexpr, or that refers to a sub-object of such an object, or
a glvalue of literal type that refers to a non-volatile temporary object whose lifetime has not ended, initialized with a constant expression;
It is interesting to note that gcc does not accept the following code (see it live):
constexpr int a = argc * 2;
So it looks like gcc is saying I know the result will be zero and therefore it performs constant folding and it does not need to perform the lvalue-to-rvalue conversion of argc
to determine the result.
Unfortunately I don't see any provisions in section 5.19
that allows this kind of short-circuiting. This looks very similar to case in int a=1, is a || 1 a constant expression? which has a bug report but no one from the gcc team has replied to that one. I added a comment to that bug report indicating this seems related.
Mark's comment below indicates this is a bug:
There is a whole c++-delayed-folding branch on which some gcc developers are working, which will delay a number of optimizations and might fix this. It is important for other reasons, rejecting the code in this question is very low priority
Do constant expressions strictly require every sub-expression to be a constant expression? No, for example from 5.19
it says: (emphasis mine)
A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression (3.2), but subexpressions of logical AND (5.14), logical OR (5.15), and conditional (5.16) operations that are not evaluated are not considered [...]
So the following is a constant expression:
constexpr int a = false && argc * 0;
Because argc * 0
is not evaluated since &&
evaluates left-to-right and short-circuits.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With