Я хочу получить тип итератора для константного содержимого вектора. Я подумал, что смогу использовать decltype для этого.
Видимо, это не так просто.
#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.
}
Я считаю, что все сводится к тому, что PoC::const_iterator относится к типу `
std::ranges::transform_view<std::ranges::ref_view<std::vector<int> >, int& (*)(int&)>::_Iterator<false>
в то время как возвращаемое значение p.cbegin() имеет тип
std::ranges::transform_view<std::ranges::ref_view<std::vector<int> >, int& (*)(int&)>::_Iterator<true>
Тип аргумента _Iterator назван _Const в исходном коде ranges.
Можете ли вы объяснить мне причину, по которой ranges так устроены? Решаема ли проблема, или мне следует создать const_iterator с нуля, а не использовать ranges?
редактировать:
Я опубликовал дополнительный вопрос https://stackoverflow.com/q/78874243/1261153: что, если мы превратим simplify в get_const, который возвращает умный указатель на const X. Удивительно, но это усложняет проблему.





Можете ли вы объяснить мне, почему
rangesтак спроектированы?Решаема ли проблема, или мне следует создать
const_iteratorс нуля, а не использоватьranges?
Я не знаю конструктивной части итераторов диапазона. Однако проблему можно исправить. Немного покопавшись, я обнаружил, что у диапазонов есть набор помощников, таких как std::ranges::const_iterator_t (начиная с c++23), который по сути помогает получить соответствующий итератор. Это означает, что проблему можно решить следующим образом:
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
};
Однако в c++20 у вас есть только опция std::ranges::iterator_t, с помощью которой вы можете сделать:
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
};
В качестве примечания: вы могли бы использовать std::as_const(начиная с C++17) в лямбда-функции вместо пользовательской функции cast_const().
@AdamRyczkowski Добро пожаловать. Я рад, что смог найти решение (кстати, могут быть альтернативы).
Для этого не нужно использовать views::transform, в стандарте уже есть специальное представление для такой утилиты, а именно views::as_const в C++23:
std::vector<int> m;
auto cm = m | std::views::as_const; // read-only view for the original vector
Замена части views::transform(cast_const) исходного примера на views::as_const также работает.
Спасибо за ответ. Жаль, что я не могу скомпилировать gcc 12.3...
Единственное, что вам нужно, чтобы решить проблему, это заменить
using const_iterator = decltype(_const.begin());
с
using const_iterator = decltype(std::as_const(_const).begin()); // needs #include <utility>
Вы не можете винить STL или диапазоны в запрете преобразования константных итераторов в неконстантные итераторы. Это проверка здравомыслия.
Большое спасибо, @JeJo! Я чувствую, что я в долгу перед тобой.