JVApen
JVApen

Reputation: 11317

Why does constructor of std::span does not limit the elements when constructed with 2 pointers/iterators

#include <iostream>
#include <span>
#include <string>
#include <string_view>
#include <vector>

namespace {
template <typename TSpan>
auto createSubSpan1(TSpan &, typename TSpan::iterator start,
                    typename TSpan::iterator stop) {
    static_assert(!std::is_convertible_v<typename TSpan::iterator, std::size_t>);
    return TSpan{start, stop};
}

template <typename TSpan>
auto createSubSpan2(TSpan &span, typename TSpan::iterator start,
                    typename TSpan::iterator stop) {
    auto startOffset = std::distance(span.begin(), start);
    auto stopOffset = std::distance(span.begin(), stop);
    std::cout << startOffset << "-" << stopOffset << std::endl;
    static_assert(!std::is_convertible_v<decltype(span.data() + startOffset), std::size_t>);
    return TSpan{span.data() + startOffset, span.data() + stopOffset};
}

template <typename TSpan>
auto createSubSpan3(TSpan &span, typename TSpan::iterator start,
                    typename TSpan::iterator stop) {
    return span.subspan(static_cast<size_t>(std::distance(span.begin(), start)),
                        static_cast<size_t>(std::distance(start, stop)));
}

template <typename TSpan>
auto printSpan(std::string_view header, TSpan span) {
    std::cout << header << std::endl;
    for (const auto &element : span) std::cout << "\t" << element << std::endl;
}
}  // namespace

int main() {
    int a[]{0, 1, 2, 3, 4, 5, 6, 7, 8};
    auto span = std::span{a};

    std::vector<int> vector{11, 12, 13};

    auto beginIt = span.begin();
    auto endIt = std::move(vector.begin(), vector.end(), beginIt);

    printSpan("incorrect (iterator)", createSubSpan1(span, beginIt, endIt));
    printSpan("incorrect (pointer)", createSubSpan2(span, beginIt, endIt));
    printSpan("correct", createSubSpan3(span, beginIt, endIt));
}


code at compiler-explorer

In this code, I would expect the createSubSpan1/2 methods to call constructor (3) of https://en.cppreference.com/w/cpp/container/span/span

template< class It, class End >
explicit(extent != std::dynamic_extent)
constexpr span( It first, End last );

However, for some reason both of them seem to print the complete span instead of the 3 elements I would expect. Variant 3 calling the subspan method does give the expected result.

Output of code with GCC, Clang and Clang+libc++:

incorrect (iterator)
    11
    12
    13
    3
    4
    5
    6
    7
    8
0-3
incorrect (pointer)
    11
    12
    13
    3
    4
    5
    6
    7
    8
correct
    11
    12
    13

As far as I can tell, both should be contiguous_iterator, the end iterator is a sized_sentinel_for the start iterator (as it is the same type), and conversion of the returned reference type of the iterator (int &) is the element type given the qualification conversion AND the iterator/pointer are not convertible to size_t (see static_assert).

So from everything I can deduce, the mentioned constructor should be chosen and give me the expected effect. Can you explain why it does not?

Related questions:

Upvotes: 4

Views: 223

Answers (1)

康桓瑋
康桓瑋

Reputation: 43026

In your example, the type of the span variable is span<int, 9>, so for createSubSpan1/2(), TSpan will be deduced as span<int, 9>, which guarantees that the returned span has 9 elements. In other words, you must ensure that stop - start is exactly 9, otherwise you will get UB ([span.cons#8]).

As for createSubSpan3(), since subspan() returns span<int, dynamic_extent>, the number of elements of the constructed span is exactly stop - start.

Upvotes: 6

Related Questions