What is the difference between `std::default_initializable` and `std::is_default_constructible`?
This is basically LWG 3149:
DefaultConstructible<T>
is equivalent toConstructible<T>
(18.4.11 [concept.constructible]), which is equivalent tois_constructible_v<T>
(20.15.4.3 [meta.unary.prop]). Per 20.15.4.3 [meta.unary.prop] paragraph 8:The predicate condition for a template specialization
is_constructible<T, Args...>
shall be satisfied if and only if the following variable definition would be well-formed for some invented variable t:T t(declval<Args>()...);
DefaultConstructible<T>
requires that objects of typeT
can be value-initialized, rather than default-initialized as intended.
The motivation for the concept is to check if you can write:
T t;
But the definition doesn't check that, it checks if you could write T()
. But T()
also doesn't mean that you can write T{}
- there are types for which those have different meaning:
struct S0 { explicit S0() = default; }; struct S1 { S0 x; }; // Note: aggregate S1 x; // Ok S1 y{}; // ill-formed; copy-list-initializes x from {}
And the intent is to simplify, for the sake of sanity, what the library has to deal with so we want to reject S1
as just being weird.
And then once the concept is checking something different from is_default_constructible
, LWG 3338 renamed it. Since, different things should have different names.
LWG issue 3338
- 3338. Rename default_constructible to default_initializable
highlights a difference in meaning between the is_default_constructible
trait and the C++20 concept originally named default_constructible
, were LWG issue 3149 to be accepted:
3149 DefaultConstructible should require default initialization
Discussion
[...] [the concept]
DefaultConstructible<T>
requires that objects of typeT
can be value-initialized, rather than default-initialized as intended.The library needs a constraint that requires object types to be default-initializable. [...]
Users will also want a mechanism to provide such a constraint, and they're likely to choose
DefaultConstructible
despite its subtle unsuitability.
Tim Song provided an example as for when the requirement of "can be value-initialized" is too weak as compared to the stricter requirement of "can be default initialized".
Tim Song noted that
{}
is not necessarily a valid initializer for aDefaultConstructible
type. In this sample program (see Compiler Explorer):struct S0 { explicit S0() = default; }; struct S1 { S0 x; }; // Note: aggregate S1 x; // Ok S1 y{}; // ill-formed; copy-list-initializes x from {}
S1
can be default-initialized, but not list-initialized from an empty braced-init-list. The consensus among those present was thatDefaultConstructible
should prohibit this class of pathological types by requiring that initialization form to be valid.
Issue 3149 has has since been moved to status WP (essentially accepted save for as a Technical Corrigendum).
Issue 3338 has subsequently also been given WP status, renaming the default_constructible
concept to default_initializable
:
3338. Rename default_constructible to default_initializable
Discussion
[...] It was made clear during discussion in LEWG that 3149 would change the concept to require default-initialization to be valid rather than value-initialization which the
is_default_constructible
trait requires. LEWG agreed that it would be confusing to have a trait and concept with very similar names yet slightly different meanings [...].Proposed resolution:
[...] Change the stable name "[concept.defaultconstructible]" to "[concept.default.init]" and retitle "Concept
default_constructible
" to "Conceptdefault_initializable
". Replace all references to the namedefault_constructible
withdefault_initializable
(There are 20 occurrences).
In short std::default_initializable<T>
requires std::is_default_constructible<T> && std::destructible<T>
, as well as a few corner cases of default construction.
Looking at the spec,
template<class T>
concept default_initializable =
std::constructible_from<T> &&
requires { T{}; } &&
requires { ::new (static_cast<void*>(nullptr)) T; };
Whereas for std::is_default_construtible
, the spec defines
If
std::is_constructible<T>::value
is true, provides the member constantvalue
equal totrue
, otherwisevalue
isfalse
.
Looking further into the definition of default_initializable
, the spec defines
template < class T, class... Args >
concept constructible_from =
std::destructible<T> &&
std::is_constructible<T, Args...>::value;
Since we are only looking at std::constructible_from<T>
then we can then see that the definition of default_initializable
can be rewritten as
template<class T>
concept default_initializable =
std::is_constructible<T>::value &&
std::destructrible<T> &&
requires { T{}; } &&
requires { ::new (static_cast<void*>(nullptr)) T; };
And finally as
template<class T>
concept default_initializable =
std::is_default_constructible<T>::value &&
std::destructrible<T> &&
requires { T{}; } &&
requires { ::new (static_cast<void*>(nullptr)) T; };