Изменяет ли 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() который обеспечивает фактическую реализацию. Это может быть сделано, но стандарт не требует этого.

Почему он этого не делает? Я не видел ни одного предложения, предлагающего эту реализацию. Если бы кто-то хотел, чтобы это было сделано, я уверен, что это было бы предложено.

Другие вопросы по тегам