Converting a pointer to a range

Viewed 126

This works:

const char* foo[] = {"This", "is", nullptr, "great"};
for (auto e : ::std::ranges::take_while_view (foo,             // <- array
                    ([](const char* s){return s!=nullptr;})))
      std::cout << e << "\n";

This doesn't:

const char* foo[] = {"This", "is", nullptr, "great"};
for (auto e : ::std::ranges::take_while_view (&foo[0],         // <- pointer
                    ([](const char* s){return s!=nullptr;})))
      std::cout << e << "\n";

error: no matching function for call to ‘take_while_view(const char**,
       main(int, char**)::<lambda(const char*)>)’

Can we, using the standard library types and functions, coerce C++ to treat a pointer as a kind of a semi-infinite range that we can then further restrict?

I can always create my own class to represent a semi-infinite view, but I'd rather find a solution in the standard library.

2 Answers

There is a way to assemble such a range from disparate parts, but you cannot implicitly treat a T* as one.

What you have to do is construct a subrange from a pointer, using unreachable_sentinel_t as the sentinel type. So you could code such a function as:

template<typename T>
auto inf_ptr_range(T *ptr)
{
  return std::ranges::subrange(ptr, std::unreachable_sentinel_t{});
}

And you can use that in your code:

const char* foo[] = {"This", "is", nullptr, "great"};
for (auto e : ::std::ranges::take_while_view (
        inf_ptr_range(foo),
        ([](const char* s){return s!=nullptr;})))
    std::cout << e << "\n";

Or using view-style notation:

int main()
{
const char* foo[] = {"This", "is", nullptr, "great"};
for (auto e : inf_ptr_range(foo) | std::views::take_while(
        [](const char* s){return s!=nullptr;}))
    std::cout << e << "\n";
}

Use iota to make an infinite range, then cut it off.

auto my_range =
    std::views::iota(&foo[0])
    | std::views::transform([](auto p) -> auto& { return *p; })
    | std::views::take_while([](auto p) { return p != nullptr; });

for(auto &ptr : my_range) {
    std::cout << ptr << "\n";
    ptr = "really";
}
for(auto &ptr : my_range) std::cout << ptr << "\n"; // mutation works!
std::cout << foo[3] << "\n";

Godbolt

I believe this is safe (that the take_while "guards" the transform and the iota so that we never try to compute &foo[0] + 5 or even access foo[3]).

Related