C++ standard allows constexpr volatile variables per defect report 1688, which was resolved in September 2013:
The combination is intentionally permitted and could be used in some circumstances to force constant initialization.
It looks though that the intention was to allow only constinit volatile, which was not available before C++20.
Still the current compilers diverge in treatment of constexpr volatile in certain circumstances. For example, this program initializes one such variable by the other one:
int main() {
constexpr volatile int i = 0;
constexpr volatile int j = i;
return j;
}
It is accepted in GCC and MSVC, but Clang complains:
error: constexpr variable 'j' must be initialized by a constant expression
constexpr volatile int j = i;
^ ~
note: read of volatile-qualified type 'const volatile int' is not allowed in a constant expression
constexpr volatile int j = i;
Online demo: https://gcc.godbolt.org/z/43ee65Peq
Which compiler is right here and why?
Clang is correct. The initialization of j from i requires that an lvalue-to-rvalue conversion be performed on i, but according to [expr.const]/5.9, an lvalue-to-rvalue conversion on a volatile glvalue is never permitted inside a constant expression. Since i is a constexpr variable, it must be initialized by a constant expression.
I have no idea why GCC and MSVC choose not to enforce this rule, other than that all C++ compilers are perpetually short-staffed and can't implement everything they're expected to.
The defect report you linked shows it should not work, so Clang is correct.
(...) “a non-volatile object defined with constexpr” (...) is permitted but that such a variable cannot appear in a constant expression. What is the intent?
But more interesting is: why does Clang care while other compilers don't?
In my opinion, this happened because of JF Bastien, a very influential figure in the world of Clang / LLVM, that personally dislikes volatile :)
He has been proposing to remove it from the language for a long time. So if it was allowed to ban volatile somewhere, he probably spared no effort to make it so. If for no other reason than simply to prevent people from writing code that will have to be rewritten if his proposal is eventually accepted.
He also made a presentation at CppCon about his deprecation proposal, if you want to know his reasoning.
Related
Say I initialize variables like this:
#include <cstdint>
constexpr uint16_t a = 65535;
constinit int64_t b = a * a; // warning: integer overflow in expression of type 'int' results in '-131071' [-Woverflow]
constexpr int64_t c = a * a; // error: overflow in constant expression [-fpermissive]
Both b and c produce undefined behavior because of integer overflow.
With constinit the variable is constant initialized. Which makes no guarantee about UB.
With constexpr the variable is initialized with a constant expression. Constant expression guarantee not to have any UB. So here the signed integer overflow in an error. But the variable is also automatically const.
So how do I best initialize a non-const variable with a constant expression?
Do I have to write
constexpr int64_t t = a * a; // error: overflow in constant expression [-fpermissive]
constinit int64_t b = t;
or
constinit int64_t b = []()consteval{ return a * a; }(); // error: overflow in constant expression
every time?
This is related to CWG issue 2543.
As it stands currently, because the compiler is allowed to replace any dynamic initialization with static initialization if it can and because constinit is only specified to enforce "no dynamic initialization", it might still allow an initializer which is not a constant expression (maybe dependent on the interpretation as discussed in the linked issue). constinit therefore reflects whether there will actually be initialization at runtime (which is relevant to avoiding dynamic initialization order issues). It does not necessarily reflect whether the initializer is a constant expression.
As stated in the issue description, this is practically not really implementable though because the dynamic/static initialization choice is made too late in the compilation process to always make constinit reflect it properly.
With one possible resolution of the issue, the specification of constinit might be changed to actually require the variable to be constant-initialized instead of just requiring that there is no dynamic initialization. If that was the resolution taken, then your first example for the initialization of b would also require the compiler to diagnose the UB and all of the other solutions would become obsolete.
The issue description doesn't seem to really favor any direction though.
For the current situation (and if the resolution is taken in another direction), an alternative to the solutions you gave is:
template<typename T>
consteval auto force_compiletime(T&& t) {
return std::forward<T>(t);
}
or
template<typename To, typename T>
consteval To force_compiletime2(T&& t) {
return std::forward<T>(t);
}
and then
constinit auto t = force_compiletime(static_cast<int64_t>(a * a));
or
constinit auto t = force_compiletime2<int64_t>(a * a);
Note that you need to include the target type in this way in the initializer, otherwise any potentially UB in the conversion will not be diagnosed. If you don't care about that
constinit int64_t t = force_compiletime(a * a);
would also be fine.
Technically the solution with the consteval lambda from your question is ill-formed, no diagnostic required, because the lambda is marked consteval but can never produce a constant expression when called. But I would expect any non-malicious compiler to still diagnose such a call.
Recently I was surprised that the following code compiles in clang, gcc and msvc too (at least with their current versions).
struct A {
static const int value = 42;
};
constexpr int f(A a) { return a.value; }
void g() {
A a; // Intentionally non-constexpr.
constexpr int kInt = f(a);
}
My understanding was that the call to f is not constexpr because the argument i isn't, but it seems I am wrong. Is this a proper standard-supported code or some kind of compiler extension?
As mentioned in the comments, the rules for constant expressions do not generally require that every variable mentioned in the expression and whose lifetime began outside the expression evaluation is constexpr.
There is a (long) list of requirements that when not satisfied prevent an expression from being a constant expression. As long as none of them is violated, the expression is a constant expression.
The requirement that a used variable/object be constexpr is formally known as the object being usable in constant expressions (although the exact definition contains more detailed requirements and exceptions, see also linked cppreference page).
Looking at the list you can see that this property is required only in certain situations, namely only for variables/objects whose lifetime began outside the expression and if either a virtual function call is performed on it, a lvalue-to-rvalue conversion is performed on it or it is a reference variable named in the expression.
Neither of these cases apply here. There are no virtual functions involved and a is not a reference variable. Typically the lvalue-to-rvalue conversion causes the requirement to become important. An lvalue-to-rvalue conversions happens whenever you try to use the value stored in the object or one of its subobjects. However A is an empty class without any state and therefore there is no value to read. When passing a to the function, the implicit copy constructor is called to construct the parameter of f, but because the class is empty, it doesn't actually do anything. It doesn't access any state of a.
Note that, as mentioned above, the rules are stricter if you use references, e.g.
A a;
A& ar = a;
constexpr int kInt = f(ar);
will fail, because ar names a reference variable which is not usable in constant expressions. This will hopefully be fixed soon to be more consistent. (see https://github.com/cplusplus/papers/issues/973)
Is it permitted to declare a non-const reference as constexpr? Example code:
int x = 1;
constexpr int& r = x;
This is accepted by gcc and clang (I tried several current and past versions of both, back to C++11, and all accepted it). However I think it should not be accepted because C++14 [dcl.constexpr/9] says:
if a constexpr specifier is used in a reference declaration, every full-
expression that appears in its initializer shall be a constant expression
and x is not a constant expression.
The language in the latest C++17 draft of [dcl.constexpr] changed and doesn't even mention constexpr references explicitly any more, I can't make head nor tail of what it is trying to say about them.
Assuming that x has static storage duration, the lvalue expression x is a perfectly valid constant expression.
If you use x in a context that requires a prvalue, which causes the lvalue-to-rvalue conversion to be applied to it, then the resulting prvalue expression - call it TO_RVALUE(x) - would not be a constant expression, for obvious reasons. But in the case of reference binding, there is no such conversion.
In the C++14 Standard (ISO/IEC 14882:2014) the word "non-mutable" was added in Section 5.19, Paragraph 2 (emphasis mine):
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, or
Therefore, this code is not correct in C++14:
class A {
public:
mutable int x;
};
int main(){
constexpr A a = {1};
constexpr int y = a.x;
return 0;
}
However, is it correct in C++11?
This is the Defect Report (CD3) 1405 where they proposed to add non-mutable:
Currently, literal class types can have mutable members. It is not clear whether that poses any particular problems with constexpr objects and constant expressions, and if so, what should be done about it.
So I would say it is correct C++11 code. Nevertheless, I tried Clang and GCC with -std=c++11 and both output an error saying mutable variables are not allowed in a constant expression. But that constraint is something added in C++14, it was not in C++11.
Does anyone know if that code is correct in C++11?
See also Defect Report (CD3) 1428.
It's C++11 defect report, then C++11 need to be fixed. Only those issues with DR, accepted, DRWP, and WP status are NOT part of the International Standard for C++.
C++11 conformed compiler must implement that DR.
For example, this pair of examples is changed because of DR 1579:
gcc 6.1.0
gcc 4.9.3
This example was taken from: Why this C++ program gives different output in C++11 & C++14 compilers
Consider the following statements
volatile int a = 7;
a; // statement A
volatile int* b = &a;
*b; // statement B
volatile int& c = a;
c; // statement C
Now, I've been trying to find a point in the standard that tells me how a compiler is to behave when coming across these statements. All I could find is that A (and possibly C) gives me an lvalue, and so does B:
"§ 5.1.1.8 Primary expressions - General" says
An identifier is an id-expression provided it has been suitably declared (Clause 7). [..]
[..] The result is the entity denoted by the identifier. The result is an
lvalue if the entity is a function, variable, or data member and a
prvalue otherwise.
[..]
"§ 5.3.1 Unary operators" says
The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points.
clang and gcc
I tried this with clang++ 3.2-11 and g++ 4.7.3, and the first produced three reads in C++11 mode and zero reads in C++03 mode (outputting three warnings) while g++ only produced the first two, explicitly warning me that the third would not be generated.
Question
It is clear which type of value comes out of the expression, from the quoted line in the standard, but:
which of the statements (A,B,C) should produce a read from the volatile entity according to the C++ standard?
The G++ warning about the "implicit dereference" comes from code in gcc/cp/cvt.c which intentionally does not load the value through a reference:
/* Don't load the value if this is an implicit dereference, or if
the type needs to be handled by ctors/dtors. */
else if (is_volatile && is_reference)
G++ does that intentionally, because as stated in the manual (When is a Volatile C++ Object Accessed?) the standard is not clear about what constitutes an access of a volatile-qualified object. As stated there you need to force lvalue-to-rvalue conversion to force a load from a volatile.
Clang gives warnings in C++03 mode that indicate a similar interpretation:
a.cc:4:3: warning: expression result unused; assign into a variable to force a volatile load [-Wunused-volatile-lvalue]
a; // statement A
^
a.cc:6:3: warning: expression result unused; assign into a variable to force a volatile load [-Wunused-volatile-lvalue]
*b; // statement B
^~
a.cc:8:3: warning: expression result unused; assign into a variable to force a volatile load [-Wunused-volatile-lvalue]
c; // statement C
^
3 warnings generated.
The G++ behaviour and the GCC manual seem to be correct for C++03, but there is a difference in C++11 relative to C++03, introduced by DR 1054 (which also explains why Clang behaves differently in C++)3 and C++11 modes). 5 [expr] p10 defines a discarded-value-expression and says that for volatiles the lvalue-to-rvalue conversion is applied to an id-expression such as your statements A and C. The spec for lvalue-to-rvalue conversion (4.1 [conv.lval]) says that the result is the value of the glvalue, which constitutes an access of the volatile. According to 5p10 all three of your statements should be accesses, so G++'s handling of statement C needs to be updated to conform to C++11. I've reported it as http://gcc.gnu.org/bugzilla/show_bug.cgi?id=59314
This gcc document 7.1 When is a Volatile C++ Object Accessed? is relevant here, and I quote (emphasis mine going forward):
The C++ standard differs from the C standard in its treatment of volatile objects. It fails to specify what constitutes a volatile access, except to say that C++ should behave in a similar manner to C with respect to volatiles
The C and C++ language specifications differ when an object is accessed in a void context:
and provides this example:
volatile int *src = somevalue;
*src;
and continues by saying:
The C++ standard specifies that such expressions do not undergo lvalue to rvalue conversion, and that the type of the dereferenced object may be incomplete. The C++ standard does not specify explicitly that it is lvalue to rvalue conversion that is responsible for causing an access.
which should be referring to draft standard section 5.3.1 Unary operators paragraph 1 which says :
The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points. [...]
and with respect to references:
When using a reference to volatile, G++ does not treat equivalent expressions as accesses to volatiles, but instead issues a warning that no volatile is accessed. The rationale for this is that otherwise it becomes difficult to determine where volatile access occur, and not possible to ignore the return value from functions returning volatile references. Again, if you wish to force a read, cast the reference to an rvalue.
so it looks like gcc is choosing to treat references to volatile differently and in order to force a read you need to cast to an rvalue, for example:
static_cast<volatile int>( c ) ;
which generates a prvalue and hence a lvalue to rvalue conversion, from section 5.2.9 Static cast:
The result of the expression static_cast(v) is the result of converting the expression v to type T. If T is an lvalue reference type or an rvalue reference to function type, the result is an lvalue; if T is an rvalue reference to object type, the result is an xvalue; otherwise, the result is a prvalue.
Update
The C++11 draft standard adds 5 Expressions paragraph 11 which says:
In some contexts, an expression only appears for its side effects. Such an expression is called a discarded-value expression. The expression is evaluated and its value is discarded. The array-to-pointer (4.2) and functionto-pointer (4.3) standard conversions are not applied. The lvalue-to-rvalue conversion (4.1) is applied if and only if the expression is an lvalue of volatile-qualified type and it is one of the following:
and includes:
— id-expression (5.1.1),
This seems ambiguous to me since with respect to a; and c; section 5.1.1 p8 says it is an lvalue and it is not obvious to me that it covers this case but as Jonathan found DR 1054 says it does indeed cover this case.