4032. Possibly invalid types in the constraints of constructors of std::shared_ptr

Section: 20.3.2.2.2 [util.smartptr.shared.const] Status: New Submitter: Jiang An Opened: 2023-12-25 Last modified: 2024-03-15

Priority: 4

View other active issues in [util.smartptr.shared.const].

View all other issues in [util.smartptr.shared.const].

View all issues with New status.

Discussion:

Currently, 20.3.2.2.2 [util.smartptr.shared.const]/3 and /9.1 says Y(*)[N] and Y(*)[], however, they may be invalid types when Y is an array type of unknown bound or a function type. Presumably, the constraints should be satisfied only when the mentioned Y(*)[N] or Y(*)[] is valid.

[2024-03-15; Reflector poll]

Set priority to 4 after reflector poll.

Jens pointed out that "convertible", as a core language concept, goes from "expression" to "type", not from "type" to "type".

Previous resolution [SUPERSEDED]:

This wording is relative to N4971.

  1. Modify 20.3.2.2.2 [util.smartptr.shared.const] as indicated:

    template<class Y> explicit shared_ptr(Y* p);
    

    -3- Constraints: When T is an array type, the expression delete[] p is well-formed and either T is U[N] and Y(*)[N] is a valid type and convertible to T*, or T is U[] and Y(*)[] is a valid type and convertible to T*. When T is not an array type, the expression delete p is well-formed and Y* is convertible to T*.

    […]

    template<class Y, class D> shared_ptr(Y* p, D d);
    template<class Y, class D, class A> shared_ptr(Y* p, D d, A a);
    template<class D> shared_ptr(nullptr_t p, D d);
    template<class D, class A> shared_ptr(nullptr_t p, D d, A a);
    

    -9- Constraints: is_move_constructible_v<D> is true, and d(p) is a well-formed expression. For the first two overloads:

    1. (9.1) — If T is an array type, then either T is U[N] and Y(*)[N] is a valid type and convertible to T*, or T is U[] and Y(*)[] is a valid type and convertible to T*.

    2. (9.2) — If T is not an array type, then Y* is convertible to T*.

    […]

[2024-03-15; Jonathan provides alternative wording]

Can we just use is_convertible_v<Y(*)[N], T*>? With enable_if-style SFINAE an invalid type will cause substitution failure and with a requires-clause the constraints won't be satisfied. Either way we get the desired outcome. Also, the delete expression is already required to be well-formed, which rules out function types, so that part of the issue is NAD.

Proposed resolution:

This wording is relative to N4971.

  1. Modify 20.3.2.2.2 [util.smartptr.shared.const] as indicated:

    template<class Y> explicit shared_ptr(Y* p);
    

    -3- Constraints: When T is an array type, the expression delete[] p is well-formed and either: T is U[N] and Y(*)[N] is convertible to T*, or T is U[] and Y(*)[] is convertible to T*.

    1. is_bounded_array_v<T> && is_convertible_v<Y(*)[rank_v<T>], T*> is true, or
    2. is_unbounded_array_v<T> && is_convertible_v<Y(*)[], T*> is true.

    When T is not an array type, the expression delete p is well-formed and Y* is convertible to T* is_convertible_v<Y*, T*> is true.

    […]

    template<class Y, class D> shared_ptr(Y* p, D d);
    template<class Y, class D, class A> shared_ptr(Y* p, D d, A a);
    template<class D> shared_ptr(nullptr_t p, D d);
    template<class D, class A> shared_ptr(nullptr_t p, D d, A a);
    

    -9- Constraints: is_move_constructible_v<D> is true, and d(p) is a well-formed expression. For the first two overloads:

    1. (9.1) — If T is an array type, then either: T is U[N] and Y(*)[N] is convertible to T*, or T is U[] and Y(*)[] is convertible to T*.

      1. is_bounded_array_v<T> && is_convertible_v<Y(*)[rank_v<T>], T*> is true, or
      2. is_unbounded_array_v<T> && is_convertible_v<Y(*)[], T*> is true.
    2. (9.2) — If T is not an array type, then Y* is convertible to T* is_convertible_v<Y*, T*> is true.

    […]