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.

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.

    […]