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.
Lacking a compiler builtin to query trivial value-initializability, we prove our assertion empirically 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 reportfalse
in practice, becauseS2
’s default constructor is user-provided and thus not readily inspectable by the front-end. Its triviality is discovered only by the optimizer.