Является ли С++ 11 изменением поведения явного вызова std:: swap, чтобы обеспечить обнаружение подкаталога с ADL, например boost:: swap?
Фон
Рассмотрим для этого вопроса следующий код:
#include <utility>
namespace ns
{
struct foo
{
foo() : i(0) {}
int i;
private:
foo(const foo&); // not defined,
foo& operator=(const foo&); // non-copyable
};
void swap(foo& lhs, foo& rhs)
{
std::swap(lhs.i, rhs.i);
}
}
template <typename T>
void do_swap(T& lhs, T& rhs); // implementation to be determined
int main()
{
ns::foo a, b;
do_swap(a, b);
}
В С++ 03 эта реализация do_swap
будет считаться "сломанной":
template <typename T>
void do_swap(T& lhs, T& rhs)
{
std::swap(lhs, rhs);
}
Явным образом указывая std::
, он запрещает поиск ns::swap
через зависящий от аргумента поиск. (Затем он не скомпилируется, потому что std::swap
пытается скопировать foo
, который не разрешен.) Вместо этого мы делаем это:
template <typename T>
void do_swap(T& lhs, T& rhs)
{
using std::swap; // allow std::swap as a backup if ADL fails to find a swap
swap(lhs, rhs); // unqualified call to swap, allow ADL to operate
}
Теперь найдено ns::swap
и std::swap
, будучи менее специализированным, не используется. Это уродливее, но оно работает и понятно в заглядывании. boost::swap
отлично обертывает это для нас (и обеспечивает перегрузку массива):
#include <boost/swap.hpp>
template <typename T>
void do_swap(T& lhs, T& rhs)
{
boost::swap(lhs, rhs); // internally does what do_swap did above
}
Вопрос
Таким образом, мой вопрос: выполняет ли std::swap
поведение boost::swap
в С++ 11? Если нет, то почему?
Мне кажется очевидным, что это должно быть. Любой код, нарушенный изменением, был, по-видимому, довольно хлипким (алгоритмы и контейнеры, такие как std::sort
и std::vector
), были недоказаны: реализациям разрешалось вызывать ADL-swap или неопределенно), поэтому изменение было бы для лучше. Кроме того, std::swap
теперь определен для массивов, поэтому изменение вообще не может быть и речи.
Однако, хотя в §17.6.3.2 указано, что все вызовы swap
в стандартной библиотеке должны выполняться без квалификации std::
(исправление проблемы с использованием алгоритмов и контейнеров, указанных выше), она не может коснуться std::swap
сам. Он даже дает примеры значений подкачки, которые включают using std::swap;
. Аналогично §20.2.2 (где указано std::swap
) не говорит ни слова об ADL.
Наконец, GCC не разрешает ADL в своей реализации std::swap
(а также MSVC, но это не говорит много). Поэтому я должен ошибаться, что std::swap
принимает поведение boost::swap
, но я не понимаю, почему это изменение не было сделано.:(И я не одинок!
Ответы
Ответ 1
Мне пришлось бы голосовать против вашей реализации концепции доказательств, если бы она была предложена. Я боюсь, что это нарушит следующий код, который, я уверен, я видел в дикой природе хотя бы раз или два за последние десять лет.
namespace oops
{
struct foo
{
foo() : i(0) {}
int i;
void swap(foo& x) {std::swap(*this, x);}
};
void swap(foo& lhs, foo& rhs)
{
lhs.swap(rhs);
}
}
Если вы считаете, что это хороший код или плохой, он работает так, как автор намеревается в С++ 98/03, и поэтому панель для тихого взлома довольно высока. Сообщая пользователям, что в С++ 11 им больше не нужно писать using std::swap;
, не является достаточно высоким преимуществом, чтобы перевешивать недостаток молчания превращения вышеуказанного кода в бесконечную рекурсию.
Другой способ выйти из записи using std::swap;
- использовать std::iter_swap
вместо этого:
template <typename T>
void do_swap(T& lhs, T& rhs)
{
std::iter_swap(&lhs, &rhs); // internally does what do_swap did above
}
Ответ 2
Вот пример реализации концепции:
#include <utility>
// exposition implementation
namespace std_
{
namespace detail
{
// actual fallback implementation
template <typename T>
void swap(T& lhs, T& rhs)
{
T temp = std::move(lhs);
lhs = std::move(rhs);
rhs = std::move(temp);
}
}
template <typename T>
void swap(T& lhs, T& rhs)
{
using detail::swap; // shadows std_::swap, stops recursion
swap(lhs, rhs); // unqualified call, allows ADL
}
}
namespace ns
{
struct foo
{
foo() : i(0) {}
int i;
private:
foo(const foo&); // not defined,
foo& operator=(const foo&); // non-copyable
};
void swap(foo& lhs, foo& rhs)
{
std::swap(lhs.i, rhs.i);
}
}
int main()
{
int i = 0, j = 0;
std_::swap(i, j);
ns::foo a, b;
std_::swap(a, b);
}
Ответ 3
Ну, boost::swap()
отправляется на std::swap()
. Чтобы std::swap()
сделать что-то похожее на boost::swap()
, ему нужно было делегировать где-то еще. Что это за другое? В стандарте не предусмотрена другая версия swap()
, которая обеспечивает фактическую реализацию. Это можно сделать, но стандарт не предусматривает его.
Почему это не так? Я не видел предложений, предлагающих эту реализацию. Если кто-то хотел, чтобы это было сделано, я уверен, что это было бы предложено.