Почему тип итератора `std::views::transform` не кажется детерминированным типом?

Я хочу получить тип итератора для константного содержимого вектора. Я подумал, что смогу использовать 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. Удивительно, но это усложняет проблему.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
76
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Можете ли вы объяснить мне, почему 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().

Большое спасибо, @JeJo! Я чувствую, что я в долгу перед тобой.

Adam Ryczkowski 14.08.2024 15:16

@AdamRyczkowski Добро пожаловать. Я рад, что смог найти решение (кстати, могут быть альтернативы).

JeJo 14.08.2024 15:17

Для этого не нужно использовать 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...

Adam Ryczkowski 14.08.2024 18:15

Единственное, что вам нужно, чтобы решить проблему, это заменить

using const_iterator = decltype(_const.begin());

с

using const_iterator = decltype(std::as_const(_const).begin()); // needs #include <utility>

Вы не можете винить STL или диапазоны в запрете преобразования константных итераторов в неконстантные итераторы. Это проверка здравомыслия.

Другие вопросы по теме