Изменяет ли C++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);
}
В C++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 или не определено), поэтому изменение будет к лучшему. Дополнительно, 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
, но я не понимаю, почему изменение не было сделано.:( И я не одинок!
3 ответа
Мне бы пришлось проголосовать против вашей реализации концепции, если бы она была предложена. Боюсь, это нарушит следующий код, который, я уверен, я видел в дикой природе, по крайней мере, один или два раза за последние дюжину лет.
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);
}
}
Считаете ли вы выше, хороший код или плохой, он работает так, как задумал автор в C++98/03, и поэтому планка для молчаливого взлома довольно высокая. Говоря пользователям, что в C++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
}
В C++20 это наконец стандартизовано:
std::swap(a, b);
Это использует ADL для вызова правильной перегрузки и предъявляет правильные требования для использования в SFINAE. Магия указана в [namespace.std] / 7:
Кроме пространства имен
std
или в пространстве имен в пространстве именstd
, программа может предоставить перегрузку для любого шаблона функции библиотеки, обозначенного как точка настройки, при условии, что (а) объявление перегрузки зависит как минимум от одного определяемого пользователем типа и (б) перегрузка соответствует требованиям стандартной библиотеки для точки настройки.174 [ Примечание: это разрешает (квалифицированный или неквалифицированный) вызов точки настройки для вызова наиболее подходящей перегрузки для данных аргументов.- конец примечания ]174) Любая точка настройки библиотеки должна быть подготовлена для адекватной работы с любой определяемой пользователем перегрузкой, которая соответствует минимальным требованиям этого документа. Следовательно, реализация может выбрать, в соответствии с правилом "как если бы" ([intro.execution]), предоставить любую точку настройки в форме созданного объекта функции ([function.objects]), даже если спецификация точки настройки находится в форме шаблона функции. Параметры шаблона каждого такого функционального объекта, а также параметры функции и возвращаемый тип объекта
operator()
должны соответствовать спецификациям соответствующей точки настройки.
(курсив мой)
А также swap
обозначен как точка настройки в [utility.swap]:
template<class T> constexpr void swap(T& a, T& b) noexcept(see below);
Примечания: эта функция является назначенной точкой настройки ([namespace.std]) и не должна участвовать в разрешении перегрузки, если только
is_move_constructible_v<T>
являетсяtrue
а такжеis_move_assignable_v<T>
являетсяtrue
. Выражение внутриnoexcept
эквивалентно:is_nothrow_move_constructible_v<T> && is_nothrow_move_assignable_v<T>
Требуется: Тип
T
должно быть Cpp17MoveConstructible (Таблица 26) и Cpp17MoveAssignable (Таблица 28).Эффекты: Обмен значений, хранящихся в двух местах.
(курсив мой)
Вот реализация концепции:
#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);
}
Что ж, boost::swap()
отправляет в std::swap()
, Иметь std::swap()
сделать что-то похожее на boost::swap()
это должно было бы делегировать куда-то еще. Что это где-то еще? Стандарт не требует другой версии swap()
который обеспечивает фактическую реализацию. Это может быть сделано, но стандарт не требует этого.
Почему он этого не делает? Я не видел ни одного предложения, предлагающего эту реализацию. Если бы кто-то хотел, чтобы это было сделано, я уверен, что это было бы предложено.