毒丸过载有两个动机,其中大多数实际上已经不存在了,但我们仍然保留它们。
swap/iter_swap
如P0370所述:
The Ranges TS has another customization point problem that N4381 does not cover: an implementation of the Ranges TS needs to co-exist alongside an implementation of the standard library. There’s little benefit to providing customization points with strong semantic constraints if ADL can result in calls to the customization points of the same name in namespace std. For example, consider the definition of the single-type Swappable concept:
namespace std { namespace experimental { namespace ranges { inline namespace v1 {
template <class T>
concept bool Swappable() {
return requires(T&& t, T&& u) {
(void)swap(std::forward<T>(t), std::forward<T>(u));
};
}
}}}}
unqualified name lookup for the name swap could find the unconstrained swap in namespace std either directly - it’s only a couple of hops up the namespace hierarchy - or via ADL if std
is an associated namespace of T
or U
. If std::swap
is unconstrained, the concept is “satisfied” for all types, and effectively useless. The Ranges TS deals with this problem by requiring changes to std::swap, a practice which has historically been forbidden for TSs. Applying similar constraints to all of the customization points defined in the TS by modifying the definitions in namespace std is an unsatisfactory solution, if not an altogether untenable.
Range TS是基于C++14构建的,其中
std::swap
没有限制(
std::swap
直到C++17采用
P0185才变得有限制),因此很重要确保
Swappable
不仅仅对于任何具有
std
作为关联命名空间的类型都可以简单地解决为true。
但现在
std::swap
已经受到限制,因此没有必要使用
swap
毒丸。
然而,
std::iter_swap
仍然没有限制,因此仍然需要使用该毒丸。但是,那个也可以轻松地变得有限制,然后我们再次不需要毒丸。
开始/结束
如
P0970所述:
为了与std :: begin兼容并方便迁移,std :: experimental :: ranges :: begin接受rvalue并将其视为const lvalue。这种行为已被弃用,因为它基本上是不合理的:由这样的重载返回的任何迭代器都很可能在包含调用begin的完整表达式之后悬挂。
另一个问题,直到最近似乎与begin的设计无关,即返回迭代器的算法将在传递给它们的范围是rvalue时将这些迭代器包装在std :: experimental :: ranges :: dangling <>中。这忽略了某些范围类型(特别是P0789的subrange <>>)的事实:迭代器的有效性根本不取决于范围的生命周期。在传递prvalue subrange <>到算法的情况下,返回包装的迭代器是完全不必要的。
[...]
我们认识到,通过从范围访问定制点中删除对rvalues的弃用默认支持,我们为范围作者提供了设计空间,以选择此行为适用于其范围类型,从而向算法传达迭代器可以安全地超出其范围类型。这消除了在传递rvalue subrange时需要dangling的情况,这是一个重要的使用场景。
这篇论文提出了一种安全调用 rvalue 的
begin
的设计,它是一个非成员函数,专门接受一个 rvalue。存在的意义在于:
template <class T>
void begin(T&&) = delete;
过载:
赋予std2::begin
这样的属性,对于某些类型为T
的右值表达式E
,表达式std2::begin(E)
只有在通过ADL找到特别接受类型为T
的右值的自由函数begin
并且该重载优先于一般的void begin(T&&)
“毒药”重载时才会编译。
例如,这将允许我们正确拒绝在类型为std::vector<int>
的右值上调用ranges::begin
,即使非成员std::begin(const C&)
可以被ADL找到。
但是这篇文章也说:
作者认为解决“子范围”和“悬挂指针”的问题需要增加一个新的特性,以使范围类型的作者有一种方法来表明其迭代器是否可以安全地超出范围。这感觉像是一个hack,而且作者无法选定这样一个特性的名称,这个名字既简洁又清晰。随后,这个功能已经通过一个特性进行检查 - 最初称为“enable_safe_range”(
P1858),现在称为“enable_borrowed_range”(
LWG3379)。因此,这里不再需要毒药丸。
=delete
语法也可用于隐藏非虚继承函数。(这不是本例中发生的情况,但它是除删除任何隐式定义的构造函数或赋值运算符之外的另一种用途。) - cdhowie