TGViewer
Грокаем C++ Грокаем C++ @grokaemcpp · 9.35K subscribers
Post #408 4.99K
std::swap и ADL

В коммьюнити есть определенное заблуждение, что когда мы вызываем 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
  • 🔥 14
  • 👍 5
  • ❤ 2
More from @grokaemcpp
  1. Oct 14, 2026​​Что внутри std::condition_variable? #опытным Более менее все знакомы с примитивом синхро…
  2. Oct 12, 2026​​WAT #новичкам Как вы думаете, результат выражения: bool math = -1 < 1U; будет правдой ил…
  3. Oct 8, 2026​​Strict weak ordering #опытным На первый взгляд, всё выглядит рабочим: мы создаём 40 зака…
  4. Oct 7, 2026​​Где-то баг... #опытным Вот вам код: struct Order { int price; int id; }; int main() { st…
  5. Oct 5, 2026Откуда spurious wakeup на кондваре? #опытным У кондваров есть метод std::condition_variabl…
  6. Oct 1, 2026​​Stacktrace. Tips #опытным Чтобы полноценно работать со стандартными трейсами, нужно знат…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →