Transparently replacing objects in constant expressions
7.7 [expr.const]
Richard Smith

Created on 2022-03-05.00:00:00 last changed 2 months ago


Date: 2022-03-27.20:51:22

7.7 [expr.const] paragraph 6 specifies that std::construct_at can be used during constant evaluation:

Similarly, the evaluation of a call to std::construct_at or std::ranges::construct_at does not disqualify E from being a core constant expression unless the first argument, of type T*, does not point to storage allocated with std::allocator<T> or to an object whose lifetime began within the evaluation of E, or the evaluation of the underlying constructor call disqualifies E from being a core constant expression.

Apparently, implementations are required to track whether an object is transparently replaceable (6.7.3 [basic.life] paragraph 8) during constant evaluation to satisfy 7.7 [expr.const] bullet 5.8, which requires that undefined behavior be detected and rejected during constant evaluation:

  • ...
  • an operation that would have undefined behavior as specified in Clause 4 through Clause 15;
  • ...

For example,

  struct A {
    int x, y;
  struct B {
    float a;
    int b;
  union C {
    A a;
    B b;
  constexpr int f() {
   C c = {};
   std::construct_at(&c.b.b, 5);
   // Does this return 5 if c.a.y and c.b.b are laid out at the same address?
   return c.a.y;

No known implementation diagnoses the violation of the rules for transparently replaceable in the following example, but there is implementation divergence for the results of f():

  #include <memory>

  struct A {
    virtual constexpr char f() { return 'A'; }
  struct B : A {
    constexpr char f() override { return 'B'; }

  constexpr char f() {
    B b;
    A *p = &b;
    return p->f();     // alternative: return b.f()
Date User Action Args
2022-03-05 00:00:00admincreate