C++20 std::Range::Sort应该不需要支持std::矢量<bool>吗?

2022-05-16 00:00:00 c++ stdvector c++20 std-ranges

我注意到std::ranges::sort无法排序std::vector<bool>

<source>:6:51: error: no match for call to '(const std::ranges::__sort_fn) (std::vector<bool, std::allocator<bool> >)'
6 |   std::ranges::sort(std::vector{false, true, true});
  |   

允许这样做吗?std::vector<bool>是否需要std::ranges::sort的专门化?有没有关于委员会如何考虑这一点的信息?

作为更新,既然采用了[3-2],论文部分将-推荐答案添加到中,使该类型满足indirectly_writable,因此std::ranges::sort上的vector<bool>可以在C++23中运行。

正确。

更一般地,std::ranges::sort不能对代理引用进行排序。直接原因是sort需要sortable(令人惊讶,对),如果我们顺着链向上需要permutable需要indirectly_movable_storable需要indirectly_movable需要indirectly_writable

indirectly_writeable是一个看起来很奇怪的概念。

template<class Out, class T>
  concept indirectly_writable =
    requires(Out&& o, T&& t) {
      *o = std::forward<T>(t);  // not required to be equality-preserving
      *std::forward<Out>(o) = std::forward<T>(t);   // not required to be equality-preserving
      const_cast<const iter_reference_t<Out>&&>(*o) =
        std::forward<T>(t);     // not required to be equality-preserving
      const_cast<const iter_reference_t<Out>&&>(*std::forward<Out>(o)) =
        std::forward<T>(t);     // not required to be equality-preserving
    };

我想特别提请您注意:

const_cast<const iter_reference_t<Out>&&>(*o) = std::forward<T>(t);

等待,我们要求常量可分配性?


这个问题由来已久。您可以从#573开始,其中用户演示了此问题:

struct C
{
    explicit C(std::string a) : bar(a) {}    
    std::string bar;
};

int main()
{
    std::vector<C> cs = { C("z"), C("d"), C("b"), C("c") };

    ranges::sort(cs | ranges::view::transform([](const C& x) {return x.bar;}));

    for (const auto& c : cs) {
        std::cout << c.bar << std::endl;
    }
}

当然,预期的是它将按该顺序打印b、c、d、z。但它没有。它打印了z,d,b,c。顺序没有改变。这里的原因是因为这是prValue的范围,我们作为排序的一部分交换元素。嗯,他们只是暂时的。这对cs没有任何影响。

这显然行不通。用户有一个错误--他们打算按bars对C进行排序(即使用投影),但实际上他们只是对bars进行排序(即使lambda返回引用,他们也只能对bar而不是Cs进行排序--在这种情况下,C只有一个成员,但在一般情况下,这显然不是预期的行为)。

但真正的目标是:我们如何使这个错误不编译?这就是我的梦想。问题是,C++在C++11中增加了引用限定,但隐式赋值一直存在。并且IMPLICIToperator=没有ref限定符,您可以很好地为r值赋值,即使这没有任何意义:

std::string("hello") = "goodbye"; // fine, but pointless, probably indicative of a bug
只有在ravlue本身正确处理的情况下,才能为r值赋值。理想情况下,我们只需检查以确保类型具有右值限定operator=。然后,代理类型(如vector<bool>::reference)将限定它们的赋值运算符,这就是我们要检查的,每个人都很高兴。

但我们不能这样做--因为基本上每个类型都是右值可赋值的,即使真正有意义的类型很少。因此,Eric和Casey想出的在道德上相当于将一个类型特征添加到一个类型中,合法地说,我是真正的、可分配的。&不像大多数类型特征,你会做这样的事情:

template <>
inline constexpr bool for_real_rvalue_assignable<T> = true;

这只是拼写:

T& operator=(Whatever) const;

即使常量相等运算符实际上不会作为算法的一部分调用。它只是必须在那里。

在这一点上,您可能会问--等等,推荐人呢?对于正常范围(例如,vector<int>),iter_reference_t<Out>提供int&,而const iter_reference_t<Out>&&是...仍然只是int&。这就是为什么这个只起作用。对于产生GL值的范围,这些常量赋值要求基本上复制了正常赋值要求。常量可分配性问题是_ONLY_FOR PRVALUES。


这个问题也是views::zip不在C++20中的驱动因素。因为zip也会产生一个pr值范围,而tuple<T&...>正是我们在这里需要处理的那种代理引用。要处理这种情况,我们必须对std::tuple进行更改以允许这种常量分配。

据我所知,这仍然是它想要的方向(假设我们已经将该要求包含在一个概念中,没有任何标准库代理类型实际满足该要求)。因此,当添加views::zip时,tuple<T&...>vector<bool>::reference将被设置为可常量分配。

这项工作的最终结果是:

std::ranges::sort(std::vector{false, true, true});

将实际编译并正常工作。

相关文章