Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 797
Adam Ryczkowski Avatar asked Sep 02 '25 15:09

Adam Ryczkowski


2 Answers

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 c++23), 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 c++20, 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.

like image 112
JeJo Avatar answered Sep 07 '25 10:09

JeJo


There is no need to use views::transform to do this, the standard already has a specific view for such utility, namely views::as_const in C++23:

std::vector<int> m;
auto cm = m | std::views::as_const; // read-only view for the original vector

Replacing the views::transform(cast_const) part of your original example with views::as_const also works.

Demo

like image 31
康桓瑋 Avatar answered Sep 07 '25 12:09

康桓瑋