This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.

4322. Problematic Constraints on incomplete types in indirect and polymorphic

Section: 20.4 [mem.composite.types] Status: New Submitter: Jonathan B. Coe Opened: 2025-08-20 Last modified: 2025-10-07

Priority: 2

View all issues with New status.

Discussion:

The class templates indirect<T> and polymorphic<T> allow the template argument T to be an incomplete type.

Both classes can be instantiated when the type T is incomplete: constraints are written so that requirements on incomplete types are not evaluated at class instantiation time.

For constructors with additional template parameters, there are currently constraints written on the potentially incomplete type T and the additional template parameters. Such constraints will not be evaluated at class instantiation time but could be explicitly evaluated in contexts where support for an incomplete T is required.

template<typename U>
class A {
  U u;
public:
  A(const SomeType&) requires std::is_constructible_v<U, SomeType> 
  {
    // […]
  }
};

when U is indirect<T> or polymorphic<T> for some type T, the existence of the requires clause will require that T is a complete type for constraints on indirect or polymorphic to be evaluated.

Constraints on T should be converted to Mandates on T so that constraint evaluation does not require T to be a complete type.

[2025-10-07; Reflector poll]

Set priority to 2 after reflector poll.

"Do we have a concrete use case of an A-like type? Is making the traits give wrong answers preferable to hard errors? I don't think the polymorphic part makes sense. Since the constraints still require U/UU to be complete, either T is a base class and it must be complete, or it is not and it doesn't matter."

"The problematic members are non-template functions, so they are instantiated early and the requires clauses are checked too soon. Think this can be solved without giving up SFINAE-friendliness, by making the affected members themselves templates."

Proposed resolution:

This wording is relative to N5014.

  1. Modify 20.4.1.3 [indirect.ctor] as indicated:

    template<class U = T>
      constexpr explicit indirect(U&& u);
    

    -17- Constraints:

    1. (17.1) — is_same_v<remove_cvref_t<U>, indirect> is false,

    2. (17.2) — is_same_v<remove_cvref_t<U>, in_place_t> is false, and

    3. (17.3) — is_constructible_v<T, U> is true, and

    4. (17.4) — is_default_constructible_v<Allocator> is true.

    -?- Mandates: is_constructible_v<T, U> is true.

    -18- Effects: […]

    template<class U = T>
      constexpr explicit indirect(allocator_arg_t, const Allocator& a, U&& u);
    

    -19- Constraints:

    1. (19.1) — is_same_v<remove_cvref_t<U>, indirect> is false, and

    2. (19.2) — is_same_v<remove_cvref_t<U>, in_place_t> is false., and

    3. (19.3) — is_constructible_v<T, U> is true

    -?- Mandates: is_constructible_v<T, U> is true.

    -20- Effects: […]

    template<class... Us>
      constexpr explicit indirect(in_place_t, Us&&... us);
    

    -21- Constraints:

    1. (21.1) — is_constructible_v<T, Us...> is true, and

    2. (21.2) — is_default_constructible_v<Allocator> is true.

    -?- Mandates: is_constructible_v<T, Us...> is true.

    -22- Effects: […]

    template<class... Us>
      constexpr explicit indirect(allocator_arg_t, const Allocator& a,
                                  in_place_t, Us&& ...us);
    

    -23- ConstraintsMandates: is_constructible_v<T, Us...> is true

    -24- Effects: […]

    template<class I, class... Us>
      constexpr explicit indirect(in_place_t, initializer_list<I> ilist, Us&&... us);
    

    -25- Constraints:

    1. (25.1) — is_constructible_v<T, initializer_list<I>&, Us...> is true, and

    2. (25.2) — is_default_constructible_v<Allocator> is true.

    -?- Mandates: is_constructible_v<T, initializer_list<I>&, Us...> is true.

    -26- Effects: […]

    template<class I, class... Us>
      constexpr explicit indirect(allocator_arg_t, const Allocator& a,
                                  in_place_t, initializer_list<I> ilist, Us&&... us);
    

    -27- ConstraintsMandates: is_constructible_v<T, initializer_list<I>&, Us...> is true

    -28- Effects: […]

  2. Modify 20.4.2.3 [polymorphic.ctor] as indicated:

    template<class U = T>
      constexpr explicit polymorphic(U&& u);
    

    -12- Constraints: Where UU is remove_cvref_t<U>,

    1. (12.1) — is_same_v<UU, polymorphic> is false,

    2. (12.2) — derived_from<UU, T> is true,

    3. (12.3) — is_constructible_v<UU, U> is true,

    4. (12.4) — is_copy_constructible_v<UU> is true,

    5. (12.5) — UU is not a specialization of in_place_type_t, and

    6. (12.6) — is_default_constructible_v<Allocator> is true.

    -?- Mandates: derived_from<UU, T> is true.

    -13- Effects: […]

    template<class U = T>
      constexpr explicit polymorphic(allocator_arg_t, const Allocator& a, U&& u);
    

    -14- Constraints: Where UU is remove_cvref_t<U>,

    1. (14.1) — is_same_v<UU, polymorphic> is false,

    2. (14.2) — derived_from<UU, T> is true,

    3. (14.3) — is_constructible_v<UU, U> is true,

    4. (14.4) — is_copy_constructible_v<UU> is true, and

    5. (14.5) — UU is not a specialization of in_place_type_t.

    -?- Mandates: derived_from<UU, T> is true.

    -15- Effects: […]

    template<class U, class... Ts>
      constexpr explicit polymorphic(in_place_type_t<U>, Ts&&... ts);
    

    -16- Constraints:

    1. (16.1) — is_same_v<remove_cvref_t<U>, U> is true,

    2. (16.2) — derived_from<U, T> is true,

    3. (16.3) — is_constructible_v<U, Ts> is true,

    4. (16.4) — is_copy_constructible_v<U> is true, and

    5. (16.5) — is_default_constructible_v<Allocator> is true.

    -?- Mandates: derived_from<U, T> is true.

    -17- Effects: […]

    template<class U, class... Ts>
      constexpr explicit polymorphic(allocator_arg_t, const Allocator& a,
                                     in_place_type_t<U>, Ts&&... ts);
    

    -18- Constraints:

    1. (18.1) — is_same_v<remove_cvref_t<U>, U> is true,

    2. (18.2) — derived_from<U, T> is true,

    3. (18.3) — is_constructible_v<U, Ts> is true, and

    4. (18.4) — is_copy_constructible_v<U> is true.

    -?- Mandates: derived_from<U, T> is true.

    -19- Effects: […]

    template<class U, class I, class... Us>
      constexpr explicit polymorphic(in_place_type_t<U>, initializer_list<I> ilist, Us&&... us);
    

    -20- Constraints:

    1. (20.1) — is_same_v<remove_cvref_t<U>, U> is true,

    2. (20.2) — derived_from<U, T> is true,

    3. (20.3) — is_constructible_v<U, initializer_list<I>&, Us...> is true,

    4. (20.4) — is_copy_constructible_v<U> is true, and

    5. (20.5) — is_default_constructible_v<Allocator> is true.

    -?- Mandates: derived_from<U, T> is true.

    -21- Effects: […]

    template<class U, class I, class... Us>
      constexpr explicit polymorphic(allocator_arg_t, const Allocator& a, 
                                     in_place_type_t<U>, initializer_list<I> ilist, Us&&... us);
    

    -22- Constraints:

    1. (22.1) — is_same_v<remove_cvref_t<U>, U> is true,

    2. (22.2) — derived_from<U, T> is true,

    3. (22.3) — is_constructible_v<U, initializer_list<I>&, Us...> is true, and

    4. (22.4) — is_copy_constructible_v<U> is true.

    -?- Mandates: derived_from<U, T> is true.

    -23- Effects: […]