Can one volatile constexpr variable initialize another one in C++?
Asked Answered
G

2

27

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?

Grisaille answered 20/11, 2022 at 20:7 Comment(1)
This looks very close to: https://mcmap.net/q/449821/-can-constexpr-be-combined-with-volatile where I explain it is not usable in a constant expression.Assist
C
17

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.

Chercherbourg answered 20/11, 2022 at 23:6 Comment(4)
In general, by my understanding, given constexpr int x=someConstExpr;, the constexpr qualifier implies that a compiler may at its option replace requests to read x with any other operation that would yield someConstExpr. If the constexpr qualifier would not invite such substitution when x is qualified volatile, what effect would that qualifier have? Without volatile, if the only function that used x also initialized some other object y of static duration, it might make sense for a compiler to have the start of the function initialize both x and y, avoiding...Smack
...the need to have some separate static initalization of x. A volatile qualfiier, however, would tell a compiler that it must presume that writes to x that occur after static initialization is complete would somehow be observable, and thus refrain from performing any.Smack
@Smack As the notes on the defect report indicate, the combination of constexpr and volatile can be used to declare a volatile variable while forcing constant initialization. It is an oddity that is not particularly likely to be useful, but its behaviour follows straightforwardly from the rules of the language.Chercherbourg
How would that differ from constexpr int two = 2; volatile int volatile_two = two;? If constexpr took priority, that would accommodate situations where it would be necessary to force a linker to include a definition which doesn't seem to be used within a program (e.g. to a allow an outside utility to inspect an executable and extract the value of a constant). The means by which outside programs might extract such information would not be contemplated by the Standard, but that doesn't mean there shouldn't be a standard means of preventing the linker from omitting it.Smack
S
12

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.

Superman answered 20/11, 2022 at 22:36 Comment(4)
In terms of what the standard should say and how compilers should behave, it makes no sense that reading the value of any volatile object can be a constant expression. One of the use-cases for volatile forcing the value to actually be read from somewhere at run-time is that shenanigans like changing the value with a debugger are possible. Maybe not actually change value for constexpr, but it's guaranteed that the read will happen at run-time, not compile-time, which rules out being part of a constant-expression.Mongolism
If JF Bastien was involved in deciding how clang handled this case, it seems no more than enforcing exactly how I'd expect constexpr volatile to work: it can live in the .rodata section, and unlike const requires its initializer to be a constant expression. But no constant-propagation from it is ever allowed, so it can't be part of other constant expressions. This is a case where reading a volatile shouldn't work, not a case of inventing justification to "ban" it from a case where it would make sense.Mongolism
The TL:DR of JF Bastien's talk is that he wants to replace volatile as a type qualifier with volatile_load and volatile_store as ways to do volatile accesses. Makes some sense to me. (As a way to limit what those can apply to, to only things that actually make sense in asm.)Mongolism
@PeterCordes: I would think a more useful meaning for the combination would be to force a compiler to treat the static initialization of the data as observable, and to say that constexpr takes precdence over volatile when the value is read. It could also be useful if e.g. the Standard were to specify that given constexpr int x=123; constexpr int y=123; a compiler would be allowed to merge the addresses, but such merging would not be allowed if either object were "volatile".Smack

© 2022 - 2024 — McMap. All rights reserved.