C++ 为什么std范围算法返回右值参数的std::ranges::悬挂,而不是。。。嗯,只是工作?

C++ 为什么std范围算法返回右值参数的std::ranges::悬挂,而不是。。。嗯,只是工作?,c++,c++20,C++,C++20,下面是我(简化)尝试实现的ranges::min_元素版本,该版本将同时适用于左值和右值参数: #include <iterator> #include <algorithm> #include <type_traits> #include <utility> namespace better_std_ranges { template<typename Range> constexpr auto min_elemen

下面是我(简化)尝试实现的
ranges::min_元素
版本,该版本将同时适用于左值和右值参数:

#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';
}
当然,它缺少一些实现细节,但思想必须明确:如果输入范围是右值,则返回值可以存储它的移动副本。 因此,
std::ranges
算法必须完全可以处理右值参数


我的问题是:为什么标准的做法正好相反,只是通过引入奇怪的
std::ranges::hanging
占位符来禁止在算法中使用右值范围?

这种方法有两个问题

首先,它打破了算法的语义。
min_元素
(以及任何其他返回迭代器的算法)的要点是将迭代器返回到范围中。您没有这样做-您正在将迭代器返回到不同的范围。这确实混淆了回报在这种情况下的含义。您甚至会将此迭代器与什么进行比较?没有相应的
.end()

第二,C++中的迭代器模型基于迭代器复制成本低的概念。每个算法都按值获取迭代器,并自由复制它们。迭代器被认为是轻量级的,重要的是,它是非拥有的。对于正向迭代器,迭代器的副本假定是可互换的

如果您突然有了一个迭代器,其中包含了它所引用的成员
std::vector
,那么这一切都会中断。复制迭代器变得非常昂贵。现在每个不同的迭代器副本实际上是一个完全不同范围的迭代器

通过让迭代器有一个成员
std::shared\u ptr
而不是
std::vector
,您可以做得更好一些。这样副本就便宜多了,不再是独立的,所以您就有了更接近合法迭代器的东西。但是现在必须进行额外的分配(以创建共享指针),仍然存在这样的问题:返回的迭代器的范围与给定的算法不同,并且算法的语义根据提供的是左值范围还是右值范围有很大不同

基本上,右值范围上的
min\u元素
需要:

  • 只需将迭代器返回到范围中,即使它将悬空
  • 在这样一个潜在的悬空迭代器周围返回某种包装(这是最初的Ranges设计,
    dangling
    仍然可以让您获得底层的
    I
  • 返回某种类型,指示此操作无效(当前设计)
  • 如果使用会导致悬空,则无法完全编译(生锈会导致什么)

我真的认为这里没有其他选择。

这种方法有两个问题

首先,它打破了算法的语义。
min_元素
(以及任何其他返回迭代器的算法)的要点是将迭代器返回到范围中。您没有这样做-您正在将迭代器返回到不同的范围。这确实混淆了回报在这种情况下的含义。您甚至会将此迭代器与什么进行比较?没有相应的
.end()

第二,C++中的迭代器模型基于迭代器复制成本低的概念。每个算法都按值获取迭代器,并自由复制它们。迭代器被认为是轻量级的,重要的是,它是非拥有的。对于正向迭代器,迭代器的副本假定是可互换的

如果您突然有了一个迭代器,其中包含了它所引用的成员
std::vector
,那么这一切都会中断。复制迭代器变得非常昂贵。现在每个不同的迭代器副本实际上是一个完全不同范围的迭代器

通过让迭代器有一个成员
std::shared\u ptr
而不是
std::vector
,您可以做得更好一些。这样副本就便宜多了,不再是独立的,所以您就有了更接近合法迭代器的东西。但是现在必须进行额外的分配(以创建共享指针),仍然存在这样的问题:返回的迭代器的范围与给定的算法不同,并且算法的语义根据提供的是左值范围还是右值范围有很大不同

基本上,右值范围上的
min\u元素
需要:

  • 只需将迭代器返回到范围中,即使它将悬空
  • 在这样一个潜在的悬空迭代器周围返回某种包装(这是最初的Ranges设计,
    dangling
    仍然可以让您获得底层的
    I
  • 返回某种类型,指示此操作无效(当前设计)
  • 如果使用会导致悬空,则无法完全编译(生锈会导致什么)

我真的认为这里没有其他选择。

我认为你可以简化这个例子。很多。相关:@Enlico如果你知道如何做得更简单,那太好了,我为你感到高兴。我只是尽我所能来说明如何解决这个问题。来自C++标准库实现的实际代码通常看起来更复杂:“@ Taras,代码中有很多东西可能与你头脑中的想法有关,而不是问题。例如,
使用差异类型=…
。写一个问题是你的责任,不是我的。话虽如此,我很高兴有人能回答。@Enlico这个想法是我问题的一个重要部分。我没想到会有人找到这样的密码
42
42