What is the difference between `std::default_initializable` and `std::is_default_constructible`?

This is basically LWG 3149:

DefaultConstructible<T> is equivalent to Constructible<T> (18.4.11 [concept.constructible]), which is equivalent to is_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 type T 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 type T 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 a DefaultConstructible 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 that DefaultConstructible 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 "Concept default_initializable". Replace all references to the name default_constructible with default_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 constant value equal to true, otherwise value is false.

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; };

Tags:

C++

C++20