From libstdc++ <concepts>
header:
namespace ranges
{
namespace __cust_swap
{
template<typename _Tp> void swap(_Tp&, _Tp&) = delete;
From MS-STL <concepts>
header:
namespace ranges {
namespace _Swap {
template <class _Ty>
void swap(_Ty&, _Ty&) = delete;
I've never encountered = delete;
outside the context where you want to prohibit the call to copy/move assignment/ctor.
I was curious if this was necessary, so I've commented out the = delete;
part from the library like this:
// template<typename _Tp> void swap(_Tp&, _Tp&) = delete;
to see if the following test case compiles.
#include <concepts>
#include <iostream>
struct dummy {
friend void swap(dummy& a, dummy& b) {
std::cout << "ADL" << std::endl;
}
};
int main()
{
int a{};
int b{};
dummy c{};
dummy d{};
std::ranges::swap(a, b);
std::ranges::swap(c, d); // Ok. Prints "ADL" on console.
}
Not only it compiles, it seems to behave well by calling user defined swap
for struct dummy
. So I'm wondering,
template<typename _Tp> void swap(_Tp&, _Tp&) = delete;
exactly do in this context? template<typename _Tp> void swap(_Tp&, _Tp&) = delete;
?TL;DR: It's there to keep from calling std::swap
.
This is actually an explicit requirement of the ranges::swap
customization point:
S
is(void)swap(E1, E2)
ifE1
orE2
has class or enumeration type ([basic.compound]) and that expression is valid, with overload resolution performed in a context that includes this definition:template<class T> void swap(T&, T&) = delete;
So what does this do? To understand the point of this, we have to remember that the ranges
namespace is actually the std::ranges
namespace. That's important because a lot of stuff lives in the std
namespace. Including this, declared in <utility>
:
template< class T >
void swap( T& a, T& b );
There's probably a constexpr
and noexcept
on there somewhere, but that's not relevant for our needs.
std::ranges::swap
, as a customization point, has a specific way it wants you to customize it. It wants you to provide a swap
function that can be found via argument-dependent lookup. Which means that ranges::swap
is going to find your swap function by doing this: swap(E1, E2)
.
That's fine, except for one problem: std::swap
exists. In the pre-C++20 days, one valid way of making a type swappable was to provide a specialization for the std::swap
template. So if you called std::swap
directly to swap something, your specializations would be picked up and used.
ranges::swap
does not want to use those. It has one customization mechanism, and it wants you to very definitely use that mechanism, not template specialization of std::swap
.
However, because std::ranges::swap
lives in the std
namespace, unqualified calls to swap(E1, E2)
can find std::swap
. To avoid finding and using this overload, it poisons the overload by making visible a version that is = delete
d. So if you don't provide an ADL-visible swap
for your type, you get a hard error. A proper customization is also required to be more specialized (or more constrained) than the std::swap
version, so that it can be considered a better overload match.
Note that ranges::begin/end
and similar functions have similar wording to shut down similar problems with similarly named std::
functions.