C++ ranges view iterators and operator->

Viewed 144

If I perform a transform view operation I noticed that the view iterator does not support the operator->. So in code terms

void transform_view( )
{
    struct Z { int a; };

    std::ranges::single_view view { Z { 99 } };

    auto transform_view { std::ranges::views::transform( view, []( auto const& z ){ return Z { z.a + 10 }; } ) };

    // The operator-> does not compile here as there is none defined
    // auto transform_view_first_value { transform_view.begin( )->a };
}

This is "as expected" as the standard in 24.7.6.3 which defines range.transform.iterator does not have an operator->.

I found this a bit surprising and tried to understand why it would not have one. I could not find and overiew in the standard or various web places of what is generally expected of view iterators and why in particular there is no operator-> on at least some of them.

Does anyone know the answer or a good source of information on this?

1 Answers

Simplifying, the prototype for the operator-> is:

T* operator->();

In order to create this operator, you must be able to return a pointer to T and this pointer must be valid after you return from the operator. So, the iterator must store a temporary object for that pointer and control its life cycle. 

This would create some problems:

  • Iterators are assumed to be cheap to store, but now they may contain a hidden copy of T. 
  • It goes against one of the design rationales: ranges cannot own elements. 
  • One of the design guidelines for ranges is that adaptors are lazy evaluated, so the object would be created when the operator-> is called. Every call to the operator should destroy the internal object and return a new pointer. If we decide to keep the object as a cache, we must add more logic to the iterator to control the life cycle and invalidation of the object. 

It also raises some questions that must be answered by the standard: Who owns that object? Is it safe to modify it? What happens when the iterator is copied? What happens when the iterator is moved? When is the pointer invalidated?

I think operator-> was not added because it makes the implementation more difficult, but it does not add any improvement: you can use operator* to achieve the same behaviour. However, this operator returns a copy of T, so the owner of the object and its life cycle is clear.

Related