Изменяет ли C ++ 11 поведение явного вызова std :: swap, чтобы обеспечить обнаружение подкачки, расположенной в ADL, например boost :: swap? - PullRequest
25 голосов
/ 07 февраля 2012

Фон

Рассмотрим для этого вопроса следующий код:

#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 в C ++ 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 ]

25 голосов
/ 17 февраля 2012

Мне бы пришлось проголосовать против вашей реализации концепции, если бы она была предложена.Боюсь, что это нарушит следующий код, который, я уверен, я видел в дикой природе, по крайней мере, один или два раза за последние дюжину лет.

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
}
3 голосов
/ 07 февраля 2012

Вот реализация концепции:

#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);
}
1 голос
/ 07 февраля 2012

Ну, boost::swap() отправляет std::swap(). Чтобы std::swap() сделал что-то похожее на boost::swap(), его нужно было бы делегировать куда-то еще. Что это где-то еще? Стандарт не требует другой версии swap(), которая обеспечивает фактическую реализацию. Это может быть сделано, но стандарт не требует этого.

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

...