The STL documentation says that the (1) uninitialized_default_construct
calls ::new (static_cast<void*>(std::addressof(*p))) Value;
.
The only difference with (2) uninitialized_value_construct
is that the later calls, ::new (static_cast<void*>(std::addressof(*p))) Value();
. (note the ()
)
I wonder what is the practical difference for both built-in types and non-trivial types.
For built-ins, it seems to me that the second (2) will do value initialization, i.e. setting the value to zero while the first (1) will be a no-op (leaving the value uninitialized.)
For non-trivial types (2) will call the default constructors. What it is not clear to me is what (1) will do for non-trivial types. Will also call the default constructor? will it be also a no-op as long as T::T() = default
and it is no-op and leave the element in a partially formed state if the the class was designed to be so?
What also confuses me is why the STL containers will always use uninitialized_value_construct
although it would have been more consistent to call uninitialized_default_construct
when possible.
For example std::vector<double>(100)
-> should call uninitialized_default_construct
while the current behavior could have been mimicked with std::vector<double>(100, {})
or std::vector<double>(100, double{})
.
Is this because the concept of uninitialized_default_construct
didn't exists in the initial version of STL? or simply there is no easy way to pass the information down to the constructor of the the container?
What also confuses me is why the STL containers will always use
uninitialized_value_construct
although it would have been more consistent to calluninitialized_default_construct
when possible.
Containers use neither. Containers construct objects via their allocators. Unfortunately, allocator_traits<...>::construct
takes an arbitrary series of parameters. Passing no parameters is ambiguous between default initialization and value initialization. Which one gets used is up to the allocator, but there is no way to select which one via the parameter list. The default allocator_traits::construct
will use value initialization, and it will always do so for all types.
the current behavior could have been mimicked with
std::vector<double>(100, {})
Even ignoring the backwards compatibility issues as well as the stuff I just said about allocators, that constructor means "default construct a double
and then copy that double
's value into 100 elements one at a time." So it would not be "the current behavior".