Reputation: 93
I use a lot this new functionality of C++20 : std::span<T>
. This is useful for writing functions that usually took a pointer and a size.
#include <array>
#include <span>
#include <vector>
std::vector<uint8_t> v1 {1,2,3,4,5};
std::array<uint8_t, 4> v2 {1,2,3,4};
uint8_t v3[3] = {1,2,3};
void f(std::span<uint8_t> arr){
...
}
/// call f with v1 or v2 or v3
f(v1);
f(v2);
f(v3);
Now, I would like to have a function that take as parameter a span of span.
std::vector<std::array<uint8_t,10>> v1;
v1.push_back({0, 1, 2, 3, 4, 5, 6, 7, 8, 9});
v1.push_back({0, 1, 2, 3, 4, 5, 6, 7, 8, 9});
v1.push_back({0, 1, 2, 3, 4, 5, 6, 7, 8, 9});
void f(std::span<std::span<uint8_t, 10>> arr){
...
}
f(v1);
Unfortunately, this is not compiling at all. This would be a way to work with 2D array.
Compilation error :
<source>:21:1: error: no matching function for call to 'f'
f(v1);
^
<source>:8:6: note: candidate function not viable: no known conversion from 'std::vector<std::array<uint8_t, 10>>' (aka 'vector<array<unsigned char, 10>>') to 'std::span<std::span<uint8_t, 10>>' (aka 'span<span<unsigned char, 10>>') for 1st argument
void f(std::span<std::span<uint8_t, 10>> arr){
^
1 error generated.
ASM generation compiler returned: 1
<source>:21:1: error: no matching function for call to 'f'
f(v1);
^
<source>:8:6: note: candidate function not viable: no known conversion from 'std::vector<std::array<uint8_t, 10>>' (aka 'vector<array<unsigned char, 10>>') to 'std::span<std::span<uint8_t, 10>>' (aka 'span<span<unsigned char, 10>>') for 1st argument
void f(std::span<std::span<uint8_t, 10>> arr){
^
1 error generated.
Execution build compiler returned: 1
Do you have any idea on how I can get this working without having to use raw pointer ?
Upvotes: 1
Views: 2748
Reputation: 275585
Spans require the thing you are spanning over to actually be there in a contiguous buffer.
There is no continguous buffer of actusl span objects there. There is a contiguous buffer of things convertible to span objects.
In this specific case, the subobjects are uniform and constsnt size, but there is a variable number of them.
One of the beauties of std span is that the conversion overhead is nearly zero; to make an actual span of spans, nearly zero (but not zero) work has to be done for each sub-span.
If this is ok with you, writing an automatic vector builder is plausible:
templafr<class T>
struct autoconverting_vector:std::vector<T> {
using base=std::vector<T>::vector;
using base::base;
template<class Range> requires( Range's iterators elements are convertible to T )
autoconverting_vector( Range&& range ): base(begin(range), end(range)) {}
};
then take an autoconverting_vector<span<uint8_t>>
Another option is to write a 2d span type; this only works cheaply when the 2nd dimension is a std array or similar, because then the sub element layout is fixed and just math.
There are n-dimensional array view types you can use for this.
Upvotes: 1
Reputation: 238381
That doesn't work because the vector doesn't contain spans. The mistake is somewhat analogous to trying to pass an int[x][y]
into function accepting int**
.
You should either:
std::span<std::array<uint8_t,10>>
to access the vectorvoid f2(std::span<std::array<uint8_t,10>> arr)
auto to_span = [](auto& arr) -> std::span<uint8_t, 10> { return {arr}; }; auto span_range = std::ranges::transform_view(v1, to_span); std::vector v2(std::begin(span_range), std::end(span_range)); f(v2);
it will not always be a vector of array
If you sometimes want to use one type of argument and other times another type of argument, then perhaps you should be defining a function template:
template<class Range> void f3(Range& range)
but I am looking for another way, because it has a certain cost O(N).
You already pay the cost of O(N) * O(M)
when you create the vector. That one O(N)
on top isn't likely to be significant.
Upvotes: 5