4

PSA: Value-initialization is not merely default-construction

 1 year ago
source link: https://quuxplusone.github.io/blog/2023/06/22/psa-value-initialization/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

PSA: Value-initialization is not merely default-construction

At the Varna WG21 meeting, Giuseppe D’Angelo presented his P2782 “Type trait to detect if value initialization can be achieved by zero-filling”. The intent of this new type trait is to tell vector’s implementor whether

std::vector<T> v;
v.resize(1000);

can just do a memset of all the T objects to put them into their correctly constructed state. By “correctly constructed state,” I mean the state a T would be in after

::new (p) T(); // value-initialization

The syntax T() causes a kind of initialization called value-initialization. Here’s a public service announcement (not for Giuseppe, who knows; but perhaps for you, dear reader; and probably for me again six months from now) —

Value-initialization is not merely default-construction!

Value-initialization for a type with a defaulted default constructor does zero-initialization first, then calls the default constructor. The effects are most visible for primitive scalar types:

int i;          // default-initialized, garbage value
int j = int();  // value-initialized, always zero

int is both trivially default-constructible (because you can default-initialize an int by default-initializing each of the unsigned chars in its object representation to garbage), and trivially value-initializable (because you can value-initialize an int by value-initializing each of the unsigned chars in its object representation to zero).

Yet “trivially default-constructible” and “trivially value-initializable” are 100% orthogonal properties. Here’s a constructive proof:

struct T;
struct S1 {
    explicit S1() = default;
    int T::*mf;
};
struct S2 {
    explicit S2() {}
    int T::*mf;
};

Struct S1 is trivially default-constructible, but it is not trivially value-initializable. Struct S2 is trivially value-initializable, but it is not trivially default-constructible.

We prove our assertion about trivial value-initializability by by writing a little function and seeing how the compiler optimizes it.

S1 f1() { return S1(); }
S2 f2() { return S2(); }

clang++ -O2 produces:

_Z2f1v:
  movq $-1, %rax
  retq
_Z2f2v:
  retq

The object representation of a value-initialized S1() is all-bits-one, but the object representation of a value-initialized S2() is not just all-bits-zero — it’s actually indeterminate! We might call that “even better than trivial.”

To focus on “better than triviality” would be a mistake: only contrived types like S2 partake of it. In fact we would expect __is_trivially_value_initializable(S2) to report false in practice, because S2’s default constructor is user-provided and thus not readily inspectable by the front-end. Its triviality is discovered only by the optimizer.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK