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
ranges
are so designed?Is the problem fixable, or should I build the
const_iterator
from 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
};
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
};
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.