Search code examples
c++algorithmiteratorc++20std-ranges

Why the type of the iterator to `std::views::transform` does not seem to be a deterministic type?


I want to get the type of the iterator to the const-casted contents of the vector. I thought I can use decltype for it.

Apparently it is not that simple.

#include <ranges>
#include <vector>

const int& cast_const(int& i)  {
   return i;
}

struct PoC {
   std::vector<int> _m;
   decltype(_m | std::views::transform(cast_const)) _const = _m | std::views::transform(cast_const);
   using const_iterator = decltype(_const.begin());
   [[nodiscard]] auto cbegin() const { return _const.begin(); }
   using iterator = decltype(_m.begin());
   [[nodiscard]] auto begin() { return _m.begin(); }
};

void test() {
   PoC p;
   PoC::const_iterator cit = p.cbegin(); // ERROR: error: conversion from ‘_Iterator<true>’ to non-scalar type ‘_Iterator<false>’ requested
   PoC::iterator it = p.begin(); // Works, because no ranges.
}

I believe it boils down to the fact, that PoC::const_iterator is of type`

std::ranges::transform_view<std::ranges::ref_view<std::vector<int> >, int& (*)(int&)>::_Iterator<false>

while the return value of p.cbegin() is of type

std::ranges::transform_view<std::ranges::ref_view<std::vector<int> >, int& (*)(int&)>::_Iterator<true>

The argument type of the _Iterator is named _Const in the source code for ranges.

Can you explain to me the reason, the ranges are so designed? Is the problem fixable, or should I build the const_iterator from scratch, and not use the ranges?


edit:

I've posted a follow-up question https://stackoverflow.com/q/78874243/1261153: what if we turn simplify into get_const that returns a smart pointer to a const X. Surprisingly this complicates the problem.


Solution

  • Can you explain to me the reason why the ranges are so designed?

    Is the problem fixable, or should I build the const_iterator from scratch, and not use the ranges?

    I do not know the design part of range iterators. However, the problem can be fixed. After digging a bit, I found out that ranges have a set of helpers like std::ranges::const_iterator_t (since ), which essentially helps to retrieve the corresponding iterator. That means, the issue can be fixed by:

    struct PoC 
    {
       std::vector<int> _m;
       const decltype(_m | std::views::transform(cast_const)) _const = ...
    // ^^^^ --> added const
    
       using const_iterator = std::ranges::const_iterator_t<decltype(_const)>;
       //                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ used to retrieve the iterator!
      
      // ...Rest of the code
    };
    

    See live demo


    In , however, you have only std::ranges::iterator_t option, by which you might do:

    
    struct PoC 
    {
       std::vector<int> _m;
       decltype(_m | std::views::transform(cast_const)) _const = ....
    
       using const_iterator = std::ranges::iterator_t<const decltype(_const)>;
       //                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
       // ...Rest of the code
    };
    

    See live demo


    As a side note, you could have make use of std::as_const(since C++17) in a lambda function, instead of the custom cast_const() function.