Search code examples
c++stdvectorstd-rangesc++23iota

Is std::ranges::transform_view::iterator not an InputIterator?


I have the following code (https://godbolt.org/z/K7sPbjjKE):

#include <string>
#include <ranges>
#include <vector>

std::vector<std::string> fields;

void insert_many(std::size_t pos, std::span<std::string> keys)
{
    auto view = std::views::iota(0uz, keys.size())
      | std::views::transform([&](std::size_t i) {
        return std::move(keys[i]);
    });
    static_assert(std::ranges::input_range<decltype(view)>);
    fields.insert(fields.cbegin() + pos, view.begin(), view.end());
}

Note: This example is oversimplified from https://godbolt.org/z/hYTjsohTf. I know, you don't have to use std::views::iota at all here. The question is about why the code in its current form doesn't work.

In both libc++ and libstdc++, the static_assert passes. However, only libc++ allows the call to insert. I am attempting to call

template< class InputIt >
constexpr iterator insert( const_iterator pos, InputIt first, InputIt last );

From the looks of the error message (with libstdc++), std::ranges::transform_view::iterator does not satisfy InputIterator so the overload cannot be called:

/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/15.0.0/../../../../include/c++/15.0.0/bits/stl_vector.h:1484:2: note: candidate template ignored: requirement 'is_convertible<std::output_iterator_tag, std::input_iterator_tag>::value' was not satisfied [with >  _InputIterator = _Iterator<false>]
 1484 |         insert(const_iterator __position, _InputIterator __first,
      |         ^

Is that the intended behavior? I thought that the new std::views stuff also satisfies the legacy iterator requirements.


Solution

  • Both libstdc++ and libc++ are correct. The code in question is simply relying on unspecified behavior and thus is non-portable.

    1. The difference type of std::views::iota(0uz, keys.size()) is required to be IOTA-DIFF-T(size_t), which is an unspecified signed-integer-like type ([range.iota.view]/(1.3)).
    2. A signed-integer-like type is either a signed_integral type or a signed-integer-class type ([iterator.concept.winc]/4).
    3. An integer-class type is one of a set of implementation-defined types (([iterator.concept.winc]/2) that supports integer operations.

    That is, the standard doesn't require std::views::iota(0uz, keys.size()) to be an integer type.

    Since the difference type of a transform_view is the same as that of its underlying view, it's also not required to be an integer type. Thus, libstdc++ is correct to use the implementation-specific __int128 as the difference type.

    On the other hand, since the standard doesn't forbid the use of integer type, libc++ is correct as well.