В коммьюнити есть определенное заблуждение, что когда мы вызываем std::swap для наших кастомных типов, эта функция в начале ищет через ADL самую подходящую перегрузку, которую мы возможно определили, и только в случае неудачи вызывается дефолтная реализация обмена. Это конечно не так и вот причины.
Первая, довольно косвенная. Все алгоритмы стандартной библиотеки никогда не используют вызов std::swap напрямую для обмена элементов последовательности. Там все делается как в предыдущем посте с помощью using std::swap, чтобы как раз разрешить ADL найти самую подходящую перегрузку. Зачем это делать, если std::swap и так самостоятельно ищет через ADL?
Вторая - подобная реализация std::swap вгоняет в бесконечную рекурсию следующий код:
namespace ns
{
struct foo
{
foo() : i(0) {}
int i;
};
void swap(foo& lhs, foo& rhs)
{
std::swap(lhs, rhs);
}
}
template <typename T>
void do_swap(T& lhs, T& rhs)
{
std::swap(lhs, rhs);
}
int main()
{
ns::foo a, b;
do_swap(a, b);
}
Неважно хороший ли это код или нет, он может встречаться в проектах. Зачем определять свою функцию swap через std::swap - загадка, но это работает.
Почему вообще должно быть важно, что работает такой плохой код? Потому что обратная совместимость. Такой код вполне мог существовать и использование ADL внутри std::swap его бы сломало.
Тем не менее были предложения в стандарт, которые предлагали разрешить ADL внутри std::swap. И они вроде даже попали в драфт С++20, но в сам стандарт так и не вошли. Думаю, что в том числе и по причине обратной совместимости.
Так что для правильного свопа необходимо приписывать using std::swap. Или нет?
Я уже говорил, что в стандартной библиотеке алгоритмов не используется обмен элементов последовательности через std::swap. Но не сказал, как именно. А обменивают их через std::iter_swap(логично, там же итераторы используются). И вот его возможная имплементация:
template<class ForwardIt1, class ForwardIt2>
constexpr //< since C++20
void iter_swap(ForwardIt1 a, ForwardIt2 b)
{
using std::swap;
swap(*a, *b);
}
То, что нужно! Теперь можно писать так:
struct my_type
{
friend void swap(my_type& first, my_type& second) noexcept {
std::iter_swap(&first.member_1, &second.member_1);
std::iter_swap(&first.member_2, &second.member_2);
}
Type1 member1;
Type2 member2;
};
Немного корявенько, зато думать не надо.
Ну или можно бустовскую версию свапа взять, которая делает примерно то же, что и iter_swap. Но надо ли вам тянуть буст - это вопрос.
Properly exchange values. Stay cool.
#cppcore