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?

Fedor
  • 17,146
  • 13
  • 40
  • 131
  • This looks very close to: https://stackoverflow.com/a/28885047/1708801 where I explain it is not usable in a constant expression. – Shafik Yaghmour Nov 22 '22 at 21:11

2 Answers2

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.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • 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... – supercat Nov 21 '22 at 18:47
  • ...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. – supercat Nov 21 '22 at 18:50
  • 1
    @supercat 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. – Brian Bi Nov 22 '22 at 00:12
  • 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. – supercat Nov 22 '22 at 16:02
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.

  • 1
    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. – Peter Cordes Nov 21 '22 at 14:29
  • 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. – Peter Cordes Nov 21 '22 at 14:34
  • 3
    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.) – Peter Cordes Nov 21 '22 at 15:22
  • @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". – supercat Nov 21 '22 at 18:25