Title
DefaultConstructible should require default initialization
Status
c++20
Section
[concept.default.init]
Submitter
Casey Carter

Created on 2018-08-09.00:00:00 last changed 37 months ago

Messages

Date: 2019-11-17.12:43:26

Proposed resolution:

This wording is relative to N4762.

  1. Modify [concept.defaultconstructible] as follows:

    template<class T>
      inline constexpr bool is-default-initializable = see below; // exposition only
    
    template<class T>
      concept DefaultConstructible = Constructible<T> && requires { T{}; } && is-default-initializable<T>;
    

    -?- For a type T, is-default-initializable<T> is true if and only if the variable definition

    T t;
    
    is well-formed for some invented variable t; otherwise it is false. Access checking is performed as if in a context unrelated to T. Only the validity of the immediate context of the variable initialization is considered.

Date: 2019-11-15.00:00:00

[ 2019-11-17; Daniel comments and restores wording ]

During the Belfast 2019 meeting the concept renaming was not voted in by this issue, but separately, the accepted wording can be found in P1917R0#3149. To prevent confusion, the here presented proposed wording has been synchronized with that of the voted in document.

Date: 2019-10-07.00:00:00

[ 2019-10-07 Casey rebases P/R onto N4830 and incorporates WG21-approved changes from P1754R1 ]

Previous resolution [SUPERSEDED]:

This wording is relative to N4762.

  1. Modify [concept.defaultconstructible] as follows:

    template<class T>
      inline constexpr bool is-default-initializable = see below; // exposition only
    
    template<class T>
      concept DefaultConstructible = Constructible<T> && requires { T{}; } && is-default-initializable<T>;
    

    -?- For a type T, is-default-initializable<T> is true if and only if the variable definition

    T t;
    
    is well-formed for some invented variable t; otherwise it is false. Access checking is performed as if in a context unrelated to T. Only the validity of the immediate context of the variable initialization is considered.

P1754R1 "Rename concepts to standard_case for C++20" - as approved by both LEWG and LWG in Cologne - contained instructions to rename the DefaultConstructible concept to default_initializable "If LWG 3151 is accepted." 3151 is the unrelated "ConvertibleTo rejects conversion from array and function types"; this issue is intended by P1754R1. Since P1754R1 was applied to the working draft in Cologne, whereas this issue was only made Ready, we should apply the desired renaming to the P/R of this issue.

Previous resolution [SUPERSEDED]:

This wording is relative to N4830.

  1. Modify [concepts.syn], header <concepts> synopsis, as indicated:

    […]
    // [concept.defaultconstructible], concept default_constructibleinitializable
    template<class T>
    concept default_constructibleinitializable = see below;
    […]
    
  2. Modify [concept.defaultconstructible] as indicated:

    18.4.12 Concept default_constructibleinitializable [concept.defaultconstructibleinitializable]

    template<class T>
      inline constexpr bool is-default-initializable = see below; // exposition only
    
    template<class T>
      concept default_constructibleinitializable = constructible_from<T> && requires { T{}; } && is-default-initializable<T>;
    

    -?- For a type T, is-default-initializable<T> is true if and only if the variable definition

    T t;
    
    is well-formed for some invented variable t; otherwise it is false. Access checking is performed as if in a context unrelated to T. Only the validity of the immediate context of the variable initialization is considered.

  3. Modify [concepts.object] as indicated:

    -1- This subclause describes concepts that specify the basis of the value-oriented programming style on which the library is based.

    template<class T>
    concept movable = is_object_v<T> && move_constructible<T> &&
    […]
    template<class T>
    concept semiregular = copyable<T> && default_constructibleinitializable<T>;
    […]
    
  4. Modify [memory.syn], header <memory> synopsis, as indicated:

    […]
    namespace ranges {
      template<no-throw-forward-iterator I, no-throw-sentinel<I> S>
        requires default_constructibleinitializable<iter_value_t<I>>
          I uninitialized_default_construct(I first, S last);
      template<no-throw-forward-range R>
        requires default_constructibleinitializable<range_value_t<R>>
          safe_iterator_t<R> uninitialized_default_construct(R&& r);
    
      template<no-throw-forward-iterator I>
        requires default_constructibleinitializable<iter_value_t<I>>
          I uninitialized_default_construct_n(I first, iter_difference_t<I> n);
    }
    […]
    namespace ranges {
      template<no-throw-forward-iterator I, no-throw-sentinel<I> S>
        requires default_constructibleinitializable<iter_value_t<I>>
         I uninitialized_value_construct(I first, S last);
      template<no-throw-forward-range R>
        requires default_constructibleinitializable<range_value_t<R>>
          safe_iterator_t<R> uninitialized_value_construct(R&& r);
    
      template<no-throw-forward-iterator I>
        requires default_constructibleinitializable<iter_value_t<I>>
          I uninitialized_value_construct_n(I first, iter_difference_t<I> n);
    }
    […]
    
  5. Modify [uninitialized.construct.default] as indicated:

    namespace ranges {
      template<no-throw-forward-iterator I, no-throw-sentinel<I> S>
        requires default_constructibleinitializable<iter_value_t<I>>
          I uninitialized_default_construct(I first, S last);
      template<no-throw-forward-range R>
        requires default_constructibleinitializable<range_value_t<R>>
          safe_iterator_t<R> uninitialized_default_construct(Ramp;& r);
    }
    

    -2- Effects: Equivalent to:

    […]

    namespace ranges {
      template<no-throw-forward-iterator I>
        requires default_constructibleinitializable<iter_value_t<I>>
          I uninitialized_default_construct_n(I first, iter_difference_t<I> n);
    }
    

    -4- Effects: Equivalent to:

    […]

  6. Modify [uninitialized.construct.value] as indicated:

    namespace ranges {
      template<no-throw-forward-iterator I, no-throw-sentinel<I> S>
        requires default_constructibleinitializable<iter_value_t<I>>
          I uninitialized_value_construct(I first, S last);
      template<no-throw-forward-range R>
        requires default_constructibleinitializable<range_value_t<R>>
          safe_iterator_t<R> uninitialized_value_construct(R&& r);
    }
    

    -2- Effects: Equivalent to:

    […]

    namespace ranges {
      template<no-throw-forward-iterator I>
        requires default_constructibleinitializable<iter_value_t<I>>
          I uninitialized_value_construct_n(I first, iter_difference_t<I> n);
    }
    

    -4- Effects: Equivalent to:

    […]

  7. Modify [range.semi.wrap] as indicated:

    -1- Many types in this subclause are specified in terms of an exposition-only class template semiregular-box. semiregular-box<T> behaves exactly like optional<T> with the following differences:

    1. (1.1) — […]

    2. (1.2) — If T models default_constructibleinitializable, the default constructor of semiregular-box<T> is equivalent to:

      constexpr semiregular-box() noexcept(is_nothrow_default_constructible_v<T>)
        : semiregular-box{in_place}
      { }
      
    3. (1.3) — […]

  8. Modify [range.istream.view], Class template basic_istream_view synopsis, as indicated:

    namespace std::ranges {
      […]
      template<movable Val, class CharT, class Traits>
        requires default_constructibleinitializable<Val> &&
          stream-extractable<Val, CharT, Traits>
      class basic_istream_view : public view_interface<basic_istream_view<Val, CharT, Traits>> {
        […]
      }
      […]
    }
    
Date: 2019-07-22.17:12:46

[ 2019 Cologne Wednesday night ]

Status to Ready

Date: 2018-10-28.00:00:00

[ 2018-10-28 Casey expands the problem statement and the P/R ]

During Batavia review of P0896R3, 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.

Date: 2018-08-23.00:00:00

[ 2018-08-23 Tim provides updated P/R based on Batavia discussion ]

Date: 2018-08-20.00:00:00

[ 2018-08-20 Priority set to 2 after reflector discussion ]

Previous resolution [SUPERSEDED]:

  1. Modify [concept.defaultconstructible] as follows:

    template<class T>
      concept DefaultConstructible = Constructible<T> && see below;
    

    -?- Type T models DefaultConstructible only if the variable definition

    T t;
    
    is well-formed for some invented variable t. Access checking is performed as if in a context unrelated to T. Only the validity of the immediate context of the variable initialization is considered.

Date: 2018-08-09.00:00:00

DefaultConstructible<T> is equivalent to Constructible<T> ([concept.constructible]), which is equivalent to is_constructible_v<T> ([meta.unary.prop]). Per [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 library needs a constraint that requires object types to be default-initializable: the "rangified" versions of the algorithms in [uninitialized.construct.default] proposed in P0896 "The One Ranges Proposal", for example. Users will also want a mechanism to provide such a constraint, and they're likely to choose DefaultConstructible despite its subtle unsuitability.

There are two alternative solutions: (1) change DefaultConstructible to require default-initialization, (2) change is_default_constructible_v to require default-initializaton and specify the concept in terms of the trait. (2) is probably too breaking a change to be feasible.

History
Date User Action Args
2021-02-25 10:48:01adminsetstatus: wp -> c++20
2019-11-19 14:48:30adminsetstatus: voting -> wp
2019-11-17 12:43:26adminsetmessages: + msg10825
2019-10-07 21:09:02adminsetmessages: + msg10686
2019-10-07 02:48:00adminsetstatus: ready -> voting
2019-07-22 17:12:46adminsetmessages: + msg10491
2019-07-22 17:12:46adminsetstatus: open -> ready
2018-10-29 01:55:13adminsetmessages: + msg10169
2018-10-29 01:55:13adminsetstatus: new -> open
2018-08-24 04:49:13adminsetmessages: + msg10107
2018-08-20 12:41:52adminsetmessages: + msg10078
2018-08-09 00:00:00admincreate
2018-08-08 23:44:07adminsetmessages: + msg10057