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

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