There were two motivations for the poison pill overloads, most of which don't actually exist anymore but we still have them anyway.
swap / iter_swap
As described in 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.
The Range TS was built on C++14, where std::swap
was unconstrained (std::swap
didn't become constrained until P0185 was adopted for C++17), so it was important to make sure that Swappable
didn't just trivially resolve to true for any type that had std
as an associated namespace.
But now std::swap
is constrained, so there's no need for the swap
poison pill.
However, std::iter_swap
is still unconstrained, so there is a need for that poison pill. However, that one could easily become constrained and then we would again have no need for a poison pill.
begin / end
As described in P0970:
For the sake of compatibility with std::begin
and ease of migration, std::experimental::ranges::begin
accepted rvalues and treated them the same as const lvalues. This behavior was deprecated because it is fundamentally unsound: any iterator returned by such an overload is highly likely to dangle after the full expression that contained the invocation ofbegin
Another problem, and one that until recently seemed unrelated to the design of begin, was that algorithms that return iterators will wrap those iterators in std::experimental::ranges::dangling<>if the range passed to them is an rvalue. This ignores the fact that for some range types — P0789’s subrange<>
, in particular — the iterator’s validity does not depend on the range’s lifetime at all. In the case where a prvalue subrange<>
is passed to an algorithm, returning a wrapped iterator is totally unnecessary.
[...]
We recognized that by removing the deprecated default support for rvalues from the range access customization points, we made design space for range authors to opt-in to this behavior for their range types, thereby communicating to the algorithms that an iterator can safely outlive its range type. This eliminates the need for dangling
when passing an rvalue subrange
, an important usage scenario.
The paper went on to propose a design for safe invocation of begin
on rvalues as a non-member function that takes, specifically, an rvalue. The existence of the:
template <class T>
void begin(T&&) = delete;
overload:
gives std2::begin
the property that, for some rvalue expression E
of type T
, the expression std2::begin(E)
will not compile unless there is a free function begin
findable by ADL that specifically accepts rvalues of type T
, and that overload is prefered by partial ordering over the general void begin(T&&)
“poison pill” overload.
For example, this would allow us to properly reject invoking ranges::begin
on an rvalue of type std::vector<int>
, even though the non-member std::begin(const C&)
would be found by ADL.
But this paper also says:
The author believed that to fix the problem with subrange
and dangling
would require the addition of a new trait to give the authors of range types a way to say whether its iterators can safely outlive the range. That felt like a hack, and that feeling was reinforced by the author’s inability to pick a name for such a trait that was sufficiently succint and clear.
Since then, this functionality has become checked by a trait - which was first called enable_safe_range
(P1858) and is now called enable_borrowed_range
(LWG3379). So again, the poison pill here is no longer necessary.