how to make a span class deduction which properly performs CTAD for rvalue arrays?

97 Views Asked by At

Background

So, my understanding is that deduction guides only work on constructors, and help in automatically deducing template parameters for the type of a constructor when the template type values are not automatically known.

In my own span class, I'm trying to make automatic deduction for my classes template parameters work from my classes constructor for rvalue std::array (and any rvalue contiguous sequence), ie so my_span{std::array{1,2,3}} works.

After running into issues with this, I realized neither GCC nor MSVC appear to allow this to work with std::span. To be clear, passing as a function parameter, or explicitly specifying types of std::span work. It just cannot seem to automatically create a std::span<const T> from a constructor call with rvalue std::array.

I had to double take trying to figure out if you could even use deduction on a raw constructor call, but it is clearly outlined in the examples given here. Below demonstrates similar functionality to what I'm asking for.

// declaration of the template
template<class T>
struct container
{
    container(T t) {}
 
    template<class Iter>
    container(Iter beg, Iter end);
};
 
// additional deduction guide
template<class Iter>
container(Iter b, Iter e) -> container<typename std::iterator_traits<Iter>::value_type>;
 
// uses
container c(7); // OK: deduces T=int using an implicitly-generated guide
std::vector<double> v = {/* ... */};
auto d = container(v.begin(), v.end()); // OK: deduces T=double
container e{5, 6}; //

Errors

My understanding is the following deduction outlined in the C++ standard should apply:

template< class T, std::size_t N >
span( const std::array<T, N>& ) -> span<const T, N>;

However, this fails with the following code on both GCC and MSVC.

#include<span> 
#include <vector> 
#include <array> 
void p(std::span<const int> s) {
}
int main(){
    std::vector<int> init = {1,2,3,4};
    std::span<int> x; 
    p(std::array<int, 3>{1,2,3}); //works
    p({std::array{1,2,3}}); //works
   auto std_span_0 = std::span(std::array{1,2,3}); //doesn't work compile errro
   std::span std_span_1(std::array{1,2,3}) //doesn't work compile error
    return 0; 
}

gcc error

<source>:12:22: error: no matching conversion for functional-style cast from 'std::array<enable_if_t<is_same_v<int, int> && is_same_v<int, int>, int>, 1 + sizeof...(_Up)>' (aka 'array<int, 1 + sizeof...(_Up)>') to 'std::span<remove_reference_t<ranges::range_reference_t<array<int, 3> &>>>' (aka 'span<int>')
   12 |    auto std_span_0 = std::span(std::array{1,2,3});
      |                      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/14.0.0/../../../../include/c++/14.0.0/span:194:2: note: candidate constructor [with _Tp = int, _ArrayExtent = 3] not viable: expects an lvalue for 1st argument
  194 |         span(array<_Tp, _ArrayExtent>& __arr) noexcept
      |         ^    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/14.0.0/../../../../include/c++/14.0.0/span:225:7: note: candidate constructor not viable: no known conversion from 'std::array<enable_if_t<is_same_v<int, int> && is_same_v<int, int>, int>, 1 + sizeof...(_Up)>' (aka 'array<int, 1 + sizeof...(_Up)>') to 'const span<int>' for 1st argument
  225 |       span(const span&) noexcept = default;
      |       ^    ~~~~~~~~~~~
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/14.0.0/../../../../include/c++/14.0.0/span:187:2: note: candidate template ignored: could not match 'type_identity_t<element_type>[_ArrayExtent]' (aka 'int[_ArrayExtent]') against 'std::array<enable_if_t<is_same_v<int, int> && is_same_v<int, int>, int>, 1 + sizeof...(_Up)>' (aka 'array<int, 1 + sizeof...(_Up)>')
  187 |         span(type_identity_t<element_type> (&__arr)[_ArrayExtent]) noexcept
      |         ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/14.0.0/../../../../include/c++/14.0.0/span:201:2: note: candidate template ignored: constraints not satisfied [with _Tp = int, _ArrayExtent = 3]
  201 |         span(const array<_Tp, _ArrayExtent>& __arr) noexcept
      |         ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/14.0.0/../../../../include/c++/14.0.0/span:199:11: note: because '__is_compatible_array<const int, 3UL>::value' evaluated to false
  199 |         requires __is_compatible_array<const _Tp, _ArrayExtent>::value
      |                  ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/14.0.0/../../../../include/c++/14.0.0/span:213:2: note: candidate template ignored: constraints not satisfied [with _Range = std::array<enable_if_t<is_same_v<int, int> && is_same_v<int, int>, int>, 1 + sizeof...(_Up)>]
  213 |         span(_Range&& __range)
      |         ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/14.0.0/../../../../include/c++/14.0.0/span:207:8: note: because '!__detail::__is_std_array<remove_cvref_t<array<int, 3> > >' evaluated to false
  207 |           && (!__detail::__is_std_array<remove_cvref_t<_Range>>)
      |               ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/14.0.0/../../../../include/c++/14.0.0/span:233:2: note: candidate template ignored: could not match 'span' against 'array'
  233 |         span(const span<_OType, _OExtent>& __s) noexcept
      |         ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/14.0.0/../../../../include/c++/14.0.0/span:149:7: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
  149 |       span() noexcept
      |       ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/14.0.0/../../../../include/c++/14.0.0/span:157:2: note: candidate constructor template not viable: requires 2 arguments, but 1 was provided
  157 |         span(_It __first, size_type __count)
      |         ^    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/14.0.0/../../../../include/c++/14.0.0/span:172:2: note: candidate constructor template not viable: requires 2 arguments, but 1 was provided
  172 |         span(_It __first, _End __last)
      |         ^    ~~~~~~~~~~~~~~~~~~~~~~~~
<source>:13:14: error: no matching constructor for initialization of 'std::span<remove_reference_t<ranges::range_reference_t<array<int, 3> &>>>' (aka 'span<int>')
   13 |    std::span std_span_1(std::array{1,2,3});
      |              ^          ~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/14.0.0/../../../../include/c++/14.0.0/span:194:2: note: candidate constructor [with _Tp = int, _ArrayExtent = 3] not viable: expects an lvalue for 1st argument
  194 |         span(array<_Tp, _ArrayExtent>& __arr) noexcept
      |         ^    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/14.0.0/../../../../include/c++/14.0.0/span:225:7: note: candidate constructor not viable: no known conversion from 'std::array<enable_if_t<is_same_v<int, int> && is_same_v<int, int>, int>, 1 + sizeof...(_Up)>' (aka 'array<int, 1 + sizeof...(_Up)>') to 'const span<int>' for 1st argument
  225 |       span(const span&) noexcept = default;
      |       ^    ~~~~~~~~~~~
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/14.0.0/../../../../include/c++/14.0.0/span:187:2: note: candidate template ignored: could not match 'type_identity_t<element_type>[_ArrayExtent]' (aka 'int[_ArrayExtent]') against 'std::array<enable_if_t<is_same_v<int, int> && is_same_v<int, int>, int>, 1 + sizeof...(_Up)>' (aka 'array<int, 1 + sizeof...(_Up)>')
  187 |         span(type_identity_t<element_type> (&__arr)[_ArrayExtent]) noexcept
      |         ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/14.0.0/../../../../include/c++/14.0.0/span:201:2: note: candidate template ignored: constraints not satisfied [with _Tp = int, _ArrayExtent = 3]
  201 |         span(const array<_Tp, _ArrayExtent>& __arr) noexcept
      |         ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/14.0.0/../../../../include/c++/14.0.0/span:199:11: note: because '__is_compatible_array<const int, 3UL>::value' evaluated to false
  199 |         requires __is_compatible_array<const _Tp, _ArrayExtent>::value
      |                  ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/14.0.0/../../../../include/c++/14.0.0/span:213:2: note: candidate template ignored: constraints not satisfied [with _Range = std::array<enable_if_t<is_same_v<int, int> && is_same_v<int, int>, int>, 1 + sizeof...(_Up)>]
  213 |         span(_Range&& __range)
      |         ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/14.0.0/../../../../include/c++/14.0.0/span:207:8: note: because '!__detail::__is_std_array<remove_cvref_t<array<int, 3> > >' evaluated to false
  207 |           && (!__detail::__is_std_array<remove_cvref_t<_Range>>)
      |               ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/14.0.0/../../../../include/c++/14.0.0/span:233:2: note: candidate template ignored: could not match 'span' against 'array'
  233 |         span(const span<_OType, _OExtent>& __s) noexcept
      |         ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/14.0.0/../../../../include/c++/14.0.0/span:149:7: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
  149 |       span() noexcept
      |       ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/14.0.0/../../../../include/c++/14.0.0/span:157:2: note: candidate constructor template not viable: requires 2 arguments, but 1 was provided
  157 |         span(_It __first, size_type __count)
      |         ^    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/14.0.0/../../../../include/c++/14.0.0/span:172:2: note: candidate constructor template not viable: requires 2 arguments, but 1 was provided
  172 |         span(_It __first, _End __last)
      |         ^    ~~~~~~~~~~~~~~~~~~~~~~~~
2 errors generated.
Compiler returned: 1

It doesn't really explain why the constructor was rejected here.

MSVC similarly fails:

example.cpp
<source>(12): error C2440: '<function-style-cast>': cannot convert from 'std::array<int,3>' to 'std::span<int,18446744073709551615>'
<source>(12): note: 'std::span<int,18446744073709551615>::span': no overloaded function could convert all the argument types
C:/data/msvc/14.39.33321-Pre/include\span(358): note: could be 'std::span<int,18446744073709551615>::span<int,3>(std::array<int,3> &) noexcept'
<source>(12): note: 'std::span<int,18446744073709551615>::span<int,3>(std::array<int,3> &) noexcept': cannot convert argument 1 from 'std::array<int,3>' to 'std::array<int,3> &'
<source>(12): note: A non-const reference may only be bound to an lvalue
C:/data/msvc/14.39.33321-Pre/include\span(379): note: or       'std::span<int,18446744073709551615>::span(const std::span<_OtherTy,_OtherExtent> &) noexcept'
C:/data/msvc/14.39.33321-Pre/include\span(366): note: or       'std::span<int,18446744073709551615>::span(_Rng &&)'
C:/data/msvc/14.39.33321-Pre/include\span(363): note: or       'std::span<int,18446744073709551615>::span(const std::array<_OtherTy,_Size> &) noexcept'
C:/data/msvc/14.39.33321-Pre/include\span(353): note: or       'std::span<int,18446744073709551615>::span(int (&)[_Size]) noexcept'
C:/data/msvc/14.39.33321-Pre/include\span(339): note: or       'std::span<int,18446744073709551615>::span(_It,_Sentinel) noexcept(<expr>)'
C:/data/msvc/14.39.33321-Pre/include\span(328): note: or       'std::span<int,18446744073709551615>::span(_It,std::span<int,18446744073709551615>::size_type) noexcept'
<source>(12): note: while trying to match the argument list '(std::array<int,3>)'
<source>(13): error C2665: 'std::span<int,18446744073709551615>::span': no overloaded function could convert all the argument types
C:/data/msvc/14.39.33321-Pre/include\span(358): note: could be 'std::span<int,18446744073709551615>::span<int,3>(std::array<int,3> &) noexcept'
<source>(13): note: 'std::span<int,18446744073709551615>::span<int,3>(std::array<int,3> &) noexcept': cannot convert argument 1 from 'std::array<int,3>' to 'std::array<int,3> &'
<source>(13): note: A non-const reference may only be bound to an lvalue
C:/data/msvc/14.39.33321-Pre/include\span(379): note: or       'std::span<int,18446744073709551615>::span(const std::span<_OtherTy,_OtherExtent> &) noexcept'
<source>(13): note: 'std::span<int,18446744073709551615>::span(const std::span<_OtherTy,_OtherExtent> &) noexcept': could not deduce template argument for 'const std::span<_OtherTy,_OtherExtent> &' from 'std::array<int,3>'
C:/data/msvc/14.39.33321-Pre/include\span(366): note: or       'std::span<int,18446744073709551615>::span(_Rng &&)'
<source>(13): note: the associated constraints are not satisfied
C:/data/msvc/14.39.33321-Pre/include\span(365): note: the concept 'std::_Span_compatible_range<std::array<int,3>,int>' evaluated to false
C:/data/msvc/14.39.33321-Pre/include\span(254): note: the constraint was not satisfied
C:/data/msvc/14.39.33321-Pre/include\span(363): note: or       'std::span<int,18446744073709551615>::span(const std::array<_OtherTy,_Size> &) noexcept'
<source>(13): note: the associated constraints are not satisfied
C:/data/msvc/14.39.33321-Pre/include\span(362): note: the constraint was not satisfied
C:/data/msvc/14.39.33321-Pre/include\span(353): note: or       'std::span<int,18446744073709551615>::span(int (&)[_Size]) noexcept'
<source>(13): note: 'std::span<int,18446744073709551615>::span(int (&)[_Size]) noexcept': could not deduce template argument for 'int (&)[_Size]' from 'std::array<int,3>'
C:/data/msvc/14.39.33321-Pre/include\span(339): note: or       'std::span<int,18446744073709551615>::span(_It,_Sentinel) noexcept(<expr>)'
<source>(13): note: 'std::span<int,18446744073709551615>::span(_It,_Sentinel) noexcept(<expr>)': expects 2 arguments - 1 provided
C:/data/msvc/14.39.33321-Pre/include\span(328): note: or       'std::span<int,18446744073709551615>::span(_It,std::span<int,18446744073709551615>::size_type) noexcept'
<source>(13): note: 'std::span<int,18446744073709551615>::span(_It,std::span<int,18446744073709551615>::size_type) noexcept': expects 2 arguments - 1 provided
<source>(13): note: while trying to match the argument list '(std::array<int,3>)'
Compiler returned: 2

Question

Can someone show me how to get something like auto x = custom_span(std::array{1,2,3}} to work to correctly produce a custom_span<const int, 3> via ctad?

2

There are 2 best solutions below

0
Jarod42 On

With just the deduction guide

template<typename T, std::size_t N>
my_span(std::array<T, N>&) -> my_span<T, N>;

template<typename T, std::size_t N>
my_span(const std::array<T, N>&) -> my_span<const T, N>;

It works as expected Demo

Issue is that constructors of span are more restricted than the only signature shown.

template< class U, std::size_t N >
constexpr span( std::array<U, N>& arr ) noexcept;
// (5)
template< class U, std::size_t N >
constexpr span( const std::array<U, N>& arr ) noexcept;
// (6)

There is also a note:

These overloads participate in overload resolution only if extent == std::dynamic_extent || N == extent is true and the conversion from std::remove_pointer_t<decltype(data(arr))> to element_type is at most a qualification conversion.

And error states that an "extra" concept fails:

the concept 'std::_Span_compatible_rangestd::array<int,3,int>' evaluated to false

rejecting the expected constructor.

0
Artyer On

The reason this doesn't work for std::span is that the last listed deduction guide is used https://en.cppreference.com/w/cpp/container/span/deduction_guides:

template< class R >
span( R&& ) -> span<std::remove_reference_t<std::ranges::range_reference_t<R>>>;

With R = std::array<int, 3>, an rvalue std::array<int, 3> is a better match for R&& than converting to const std::array<int, 3>& for the other deduction guide.

This then means that std::span(std::array{1,2,3}) deduces int for T, and not const int (and std::dynamic_extent instead of 3 for the extent), which an rvalue array is not convertible to.

If there were a deduction guide like:

template< class T, std::size_t N >
span( std::array<T, N>&& ) -> span<const T, N>;

This would allow std::span(std::array{1, 2, 3}) (to be std::span<const int, 3>(std::array{1, 2, 3})). But this might not be desirable since the array is immediately destroyed and you are left with a dangling reference.