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

### 3369. `span`'s deduction-guide for built-in arrays doesn't work

Section: 24.7.3.1 [span.overview] Status: C++20 Submitter: Stephan T. Lavavej Opened: 2020-01-08 Last modified: 2021-02-25

Priority: 0

View other active issues in [span.overview].

View all other issues in [span.overview].

View all issues with C++20 status.

Discussion:

N4842 22.7.3.1 [span.overview] depicts:

>     template<class T, size_t N>
>     span(T (&)[N]) -> span<T, N>;

This isn't constrained by 22.7.3.3 [span.deduct]. Then, 22.7.3.2 [span.cons]/10 specifies:

>     template<size_t N> constexpr span(element_type (&arr)[N]) noexcept;
>     template<size_t N> constexpr span(array<value_type, N>& arr) noexcept;
>     template<size_t N> constexpr span(const array<value_type, N>& arr) noexcept;

> > Constraints:

> > - `extent == dynamic_extent || N == extent` is `true`, and

> > - `remove_pointer_t<decltype(data(arr))>(*)[]` is convertible to `ElementType(*)[]`.

Together, these cause CTAD to behave unexpectedly. Here's a minimal test case, reduced from libcxx's test suite:

>     C:\Temp>type span_ctad.cpp
>     #include <stddef.h>
>     #include <type_traits>

>     inline constexpr size_t dynamic_extent = static_cast<size_t>(-1);

>     template <typename T, size_t Extent = dynamic_extent>
>     struct span {
>       template <size_t Size>
>       requires (Extent == dynamic_extent || Extent == Size)
>     #ifdef WORKAROUND_WITH_TYPE_IDENTITY_T
>       span(std::type_identity_t<T> (&)[Size]) {}
>     #else
>       span(T (&)[Size]) {}
>     #endif
>     };

>     template <typename T, size_t Extent>
>     #ifdef WORKAROUND_WITH_REQUIRES_TRUE
>     requires (true)
>     #endif
>     span(T (&)[Extent]) -> span<T, Extent>;

>     int main() {
>       int arr[] = {1,2,3};
>       span s{arr};
>       static_assert(std::is_same_v<decltype(s), span<int, 3>>,
>         "CTAD should deduce span<int, 3>.");
>     }

>     C:\Temp>cl /EHsc /nologo /W4 /std:c++latest span_ctad.cpp
>     span_ctad.cpp
>     span_ctad.cpp(26): error C2338: CTAD should deduce span<int, 3>.

>     C:\Temp>cl /EHsc /nologo /W4 /std:c++latest /DWORKAROUND_WITH_TYPE_IDENTITY_T span_ctad.cpp
>     span_ctad.cpp

>     C:\Temp>cl /EHsc /nologo /W4 /std:c++latest /DWORKAROUND_WITH_REQUIRES_TRUE span_ctad.cpp
>     span_ctad.cpp

>     C:\Temp>

(MSVC and GCC 10 demonstrate this behavior. Clang is currently affected by LLVM#44484.)

Usually, when there's an explicit deduction-guide, we can ignore any corresponding constructor, because the overload resolution tiebreaker 12.4.3 [over.match.best]/2.10 prefers deduction-guides. However, this is a mental shortcut only, and it's possible for guides generated from constructors to out-compete deduction-guides during CTAD. That's what's happening here.

Specifically, the constructor is constrained, while the deduction-guide is not constrained. This activates the "more specialized" tiebreaker first (12.4.3 [over.match.best]/2.5 is considered before /2.10 for deduction-guides). That goes through 13.7.6.2 [temp.func.order]/2 and 13.5.4 [temp.constr.order] to prefer the more constrained overload.

(In the test case, this results in `span<int, dynamic_extent>` being deduced. That's because the constructor allows `T` to be deduced to be `int`. The constructor's `Size` template parameter is deduced to be 3, but that's unrelated to the class's `Extent` parameter. Because `Extent` has a default argument of `dynamic_extent`, CTAD succeeds and deduces `span<int, dynamic_extent>`.)

There are at least two possible workarounds: we could alter the constructor to prevent it from participating in CTAD, or we could constrain the deduction-guide, as depicted in the test case. Either way, we should probably include a Note, following the precedent of 21.3.2.2 [string.cons]/12.

Note that there are also deduction-guides for span from `std::array`. However, the constructors take `array<value_type, N>` with `using value_type = remove_cv_t<ElementType>;` so that prevents the constructors from interfering with CTAD.

I'm currently proposing to alter the constructor from built-in arrays. An alternative resolution to constrain the deduction-guide would look like: "Constraints: `true`. [Note: This affects class template argument deduction. — end note]"

[2020-01-25 Status set to Tentatively Ready after seven positive votes on the reflector.]

Proposed resolution:

This wording is relative to N4842.

1. Modify 24.7.3.1 [span.overview], class template `span` synopsis, as indicated:

   >     namespace std {
   >     template<class ElementType, size_t Extent = dynamic_extent>
   >     class span {
   >     public:
   >       […]
   >       // 24.7.3.2 [span.cons], constructors, copy, and assignment
   >       constexpr span() noexcept;
   >       […]
   >       template<size_t N>
   >       constexpr span(type_identity_t<element_type> (&arr)[N]) noexcept;
   >       […]
   >     };
   >     […]

2. Modify 24.7.3.2 [span.cons] as indicated:

   >     template<size_t N> constexpr span(type_identity_t<element_type> (&arr)[N]) noexcept;
   >     template<size_t N> constexpr span(array<value_type, N>& arr) noexcept;
   >     template<size_t N> constexpr span(const array<value_type, N>& arr) noexcept;

   > > -10- Constraints:

   > > 1. (10.1) — `extent == dynamic_extent || N == extent` is `true`, and

   > > 2. (10.2) — `remove_pointer_t<decltype(data(arr))>(*)[]` is convertible to `ElementType(*)[]`.

   > > -11- Effects: Constructs a `span` that is a view over the supplied array. [Note: `type_identity_t` affects class template argument deduction. — end note]

   > > -12- Postconditions: `size() == N && data() == data(arr)`.