簡體   English   中英

為什么標准范圍算法返回右值 arguments 的 std::ranges::dangling 而不是......好吧,只是工作?

[英]Why do std range algorithms return std::ranges::dangling for rvalue arguments instead of… well, just working?

這是我(簡化)嘗試實現適用於左值和右值 arguments 的ranges::min_element版本:

#include <iterator>
#include <algorithm>
#include <type_traits>
#include <utility>

namespace better_std_ranges
{
    template<typename Range>
    constexpr auto min_element(Range& range)
    {
        using std::begin;
        using std::end;
        return std::min_element(begin(range), end(range));
    }

    template<typename Range>
    constexpr auto min_element(Range&& range)
    {
        static_assert(!std::is_reference_v<Range>, "wrong overload chosen");

        class _result_iterator_type // todo: inherit from some crtp base that will provide lacking operators depending on _underlying_iterator_type::iterator_category
        {
            using _underlying_iterator_type = std::decay_t<decltype(std::begin(std::declval<Range&>()))>;

        public:
            explicit constexpr _result_iterator_type(Range&& range) noexcept(std::is_nothrow_move_constructible_v<Range>)
            : _underlying_range{std::move(range)}
            , _underlying_iterator(::better_std_ranges::min_element(_underlying_range))
            {
            }

            using difference_type   = typename _underlying_iterator_type::difference_type;
            using value_type        = typename _underlying_iterator_type::value_type;
            using pointer           = typename _underlying_iterator_type::pointer;
            using reference         = typename _underlying_iterator_type::reference;
            using iterator_category = typename _underlying_iterator_type::iterator_category;

            constexpr decltype(auto) operator*() const
            {
                return *_underlying_iterator;
            }

            // todo: define other member functions that were not provided by the inheritance above

        private:
            Range _underlying_range;
            _underlying_iterator_type _underlying_iterator;
        };

        return _result_iterator_type{std::move(range)};
    }
}

#include <vector>
#include <iostream>

auto make_vector()
{
    return std::vector{100, 200, 42, 500, 1000};
}

int main()
{
    auto lvalue_vector = make_vector();
    auto lvalue_vector_min_element_iterator = better_std_ranges::min_element(lvalue_vector);
    std::cout << *lvalue_vector_min_element_iterator << '\n';

    auto rvalue_vector_min_element_iterator = better_std_ranges::min_element(make_vector());
    std::cout << *rvalue_vector_min_element_iterator << '\n';
}

output 是

42
42

當然它缺少一些實現細節,但思路必須清楚:如果輸入范圍是右值,則返回值可以存儲它的移動副本。 因此, std::ranges算法必須完全有可能與右值 arguments 一起使用。

我的問題是:為什么標准 go 以相反的方式通過引入奇怪的std::ranges::dangling占位符來禁止在其算法中使用右值范圍?

這種方法有兩個問題。

首先,它破壞了算法的語義。 min_element (以及任何其他返回迭代器的算法)的要點是將迭代器返回范圍中。 您沒有這樣做 - 您將迭代器返回到不同的范圍。 在這種情況下,這確實混淆了回報甚至意味着什么的概念。 您甚至會將這個迭代器與什么進行比較? 沒有對應的.end()

其次,C++ 中的迭代器 model 非常強烈地基於迭代器易於復制的概念。 每個算法都按值獲取迭代器並自由復制它們。 迭代器被認為是輕量級的,重要的是,它是非擁有的。 對於前向迭代器,假設迭代器的副本是可互換的。

如果您突然有一個迭代器,該迭代器具有它所引用的成員std::vector<T> ,那么這一切都會中斷。 復制迭代器變得非常昂貴。 現在每個不同的迭代器副本實際上都是一個完全不同范圍的迭代器?

通過讓迭代器有一個成員std::shared_ptr<std::vector<T>>而不是std::vector<T> ,您可以做得更好。 這種方式副本更便宜且不再獨立,因此您擁有更接近合法迭代器的東西。 但是現在您必須進行額外的分配(以創建共享指針),您仍然會遇到問題,即您返回的迭代器與給定算法的范圍不同,並且您遇到的問題是算法非常根據您是否提供左值或右值范圍而具有不同的語義。

基本上,右值范圍上的min_element需要:

  • 只需將迭代器返回到范圍內,即使它會懸空
  • 在這樣一個可能懸空的迭代器周圍返回某種包裝器(這是原始的 Ranges 設計, dangling<I>仍然可以讓你得到底層的I
  • 返回某種類型,表明這不起作用(當前設計)
  • 如果使用會導致懸空,則無法完全編譯(Rust 將允許)

我認為這里沒有其他選擇,真的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM