作为更新,现在c++23zip
采用了该方法,该论文的一部分添加了-assignment to ,它允许该类型满足,因此适用于 C++23 中的作品。const
vector<bool>::reference
indirectly_writable
std::ranges::sort
vector<bool>
正确的。
更一般地说,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);
等等,我们需要const可分配性?
这个特殊的问题由来已久。您可以从#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。顺序没有改变。这里的原因是因为这是一个纯右值范围,我们交换的元素是排序的一部分。嗯,他们是临时的。这对cs
任何事情都没有影响。
这显然行不通。用户有一个错误 - 他们打算按C
s 对bar
s 进行排序(即使用投影),但他们只是对bar
s 进行排序(即使 lambda 返回一个引用,他们也只会对bar
s 进行排序而不是C
无论如何——在这种情况下,无论如何只有一个成员,C
但在一般情况下,这显然不是预期的行为)。
但目标确实是:我们如何使这个错误无法编译?这就是梦想。问题是 C++ 在 C++11 中添加了 ref-qualifications,但隐式赋值一直存在。并且隐式operator=
没有引用限定符,您可以很好地分配给右值,即使这没有任何意义:
std::string("hello") = "goodbye"; // fine, but pointless, probably indicative of a bug
仅当 ravlue 本身正确处理此问题时,才真正可以分配给右值。理想情况下,我们可以检查以确保类型具有 rvalue-qualified operator=
。然后,代理类型(例如vector<bool>::reference
)将限定它们的赋值运算符,这就是我们要检查的内容,并且每个人都很高兴。
但我们不能这样做——因为基本上每种类型都是右值可赋值的,即使很少有类型实际上是有意义的。因此,Eric 和 Casey 的想法在道德上等同于将类型特征添加到“我是,合法地,真实地,可右值赋值”的类型。与大多数类型特征不同,您会执行以下操作:
template <>
inline constexpr bool for_real_rvalue_assignable<T> = true;
这个只是拼写:
T& operator=(Whatever) const;
即使 const 相等运算符实际上不会作为算法的一部分被调用。它必须在那里。
此时您可能会问 - 等等,参考文献呢?对于“正常”范围(例如 ,vector<int>
给iter_reference_t<Out>
你int&
,并且const iter_reference_t<Out>&&
......仍然只是int&
。这就是为什么这只是工作。对于产生glvalues的范围,这些const-assignment要求基本上复制了正常的分配要求。const-assignability问题是_only_for prvalues。
这个问题也是为什么views::zip
不在 C++20 中的驱动因素。因为zip
还会产生一个纯右值范围,而 atuple<T&...>
正是我们需要在此处处理的那种代理引用。为了解决这个问题,我们必须进行更改std::tuple
以允许这种 const 可分配性。
据我所知,这仍然是它的预期方向(鉴于我们已经将该要求纳入一个概念,这是一个没有标准库代理类型实际满足的要求)。所以当views::zip
被添加时,tuple<T&...>
将成为 const-assignable 以及vector<bool>::reference
.
这项工作的最终结果是:
std::ranges::sort(std::vector{false, true, true});
实际上会编译和正常工作。