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.
Can you explain to me the reason why the
rangesare so designed?Is the problem fixable, or should I build the
const_iteratorfrom scratch, and not use theranges?
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With