cpp-ru / ideas

Идеи по улучшению языка C++ для обсуждения
https://cpp-ru.github.io/proposals
Creative Commons Zero v1.0 Universal
89 stars 0 forks source link

std::ranges не должны игнорировать & && перегрузки begin / end #497

Open kelbon opened 2 years ago

kelbon commented 2 years ago

Если в случае range based for loop игнорировать такие перегрузки ещё адекватно, то в случае ranges это приводит к проблемам, например, пусть у меня есть условный генератор - корутина, тогда в в случае && для интеграции с std::ranges я бы мог перегрузить begin для &&,, создав владеющий итератор(владеющий coroutine_handle) При этом метод end() является статическим, поэтому никаких проблем с вызовом end после вызова перегруженного для && метода нет.

// здесь owner - coroutine_handle
  auto begin() && {
    struct iterator_owner : iterator {
      using iterator::iterator;
      iterator_owner(iterator_owner&& other) noexcept : owner(std::exchange(other.owner, nullptr)) {}
      iterator_owner& operator=(iterator_owner&& other) noexcept { std::swap(owner, other.owner); }
      ~iterator_owner() {
        if (owner) owner.destroy();
      }
    };
    this->my_handle.resume();
    return iterator_owner{{this->release()}};
  }

Тогда пользователь мог бы безопасно писать

template <typename T, typename Alloc>
constexpr bool ::std::ranges::enable_borrowed_range<kelbon::generator<T, Alloc>> = true;

void Foo() {
  auto make_generator = []() -> kelbon::generator<int> {
    for (auto i = 0; i < 100; ++i)
      co_yield i;
  };
  for (auto v : make_generator() | std::views::all)
    std::cout << v;
}

Сейчас произойдёт UB, т.к. внутри ranges не происходит forward ренжа, и выберется не та перегрузка begin, скорее всего рантайм ошибка(в лучшем случае)

Как я предлагаю это исправить: Исправить текст стандарта

    template<borrowed_­range R>
      requires convertible-to-non-slicing<iterator_t<R>, I> &&
               convertible_­to<sentinel_t<R>, S>
    constexpr subrange(R&& r, make-unsigned-like-t<iter_difference_t<I>> n)
      requires (K == subrange_kind::sized)
        : subrange{ranges::begin(r), ranges::end(r), n} {}

https://eel.is/c++draft/range.subrange#ctor-6.2 https://eel.is/c++draft/range.subrange#access-10 Добавив std::forward<decltype(r)>

Либо добавить перегрузки с requires, если будет страшно за abi

apolukhin commented 2 years ago

Если нужно чтобы диапазон завладел контейнером можно воспользоваться std::ranges::owning_view:

for (auto v : views::owning_view{make_generator()})
    std::cout << v;

И работает без специализации enable_borrowed_range. Или вам нужно что-то другое?

kelbon commented 2 years ago

Если нужно чтобы диапазон завладел контейнером можно воспользоваться std::ranges::owning_view:

for (auto v : views::owning_view{make_generator()})
    std::cout << v;

И работает без специализации enable_borrowed_range. Или вам нужно что-то другое?

  1. тот кто использует генератор вполне может об этом не знать или забыть.
  2. This specialization of std::ranges::enable_borrowed_range makes owning_view satisfy borrowed_range when the underlying range satisfies it. Что означает, что будет ошибка компиляции в выражении из примера(в вашем примере всё будет хорошо в любом случае, даже без ренжей). Потому что сейчас генератор нельзя сделать borrowed_range по вот такой вот причине с begin&&