span
's deduction-guide for built-in arrays doesn't workSection: 23.7.2.2.1 [span.overview] Status: C++20 Submitter: Stephan T. Lavavej Opened: 2020-01-08 Last modified: 2021-02-25
Priority: 0
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
istrue
, and
remove_pointer_t<decltype(data(arr))>(*)[]
is convertible toElementType(*)[]
.
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 inspan<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.
Modify 23.7.2.2.1 [span.overview], class template span
synopsis, as indicated:
namespace std { template<class ElementType, size_t Extent = dynamic_extent> class span { public: […] // 23.7.2.2.2 [span.cons], constructors, copy, and assignment constexpr span() noexcept; […] template<size_t N> constexpr span(type_identity_t<element_type> (&arr)[N]) noexcept; […] }; […]
Modify 23.7.2.2.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:
-11- Effects: Constructs a
(10.1) —
extent == dynamic_extent || N == extent
istrue
, and(10.2) —
remove_pointer_t<decltype(data(arr))>(*)[]
is convertible toElementType(*)[]
.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)
.