Используя директиву vs с использованием декларации swap в С++

Обратитесь к приведенному ниже коду:

#include <algorithm>

namespace N
{

    template <typename T>
    class C
    {
    public:
        void SwapWith(C & c)
        {
            using namespace std; // (1)
            //using std::swap;   // (2)
            swap(a, c.a);
        }
    private:
        int a;
    };

    template <typename T>
    void swap(C<T> & c1, C<T> & c2)
    {
        c1.SwapWith(c2);
    }

}

namespace std
{

    template<typename T> void swap(N::C<T> & c1, N::C<T> & c2)
    {
        c1.SwapWith(c2);
    }

}

Как написано выше, код не компилируется в Visual Studio 2008/2010. Ошибка:

'void N::swap(N::C<T> &,N::C<T> &)' : could not deduce template argument for 'N::C<T> &' from 'int'.

Однако, если я прокомментирую (1) и раскомментирую (2), он скомпилирует ОК. В чем разница между using namespace std и using std::swap, которая объясняет это поведение?

Ответ 1

Первый случай - это директива using (using namespace X), и это означает, что имена из пространства имен X будут доступны для регулярного поиска в первом общем пространстве имен X и текущей области. В этом случае первым общим предком пространства имен ::N и ::std является ::, поэтому директива using сделает std::swap доступной только в том случае, если обратные образы ::.

Проблема заключается в том, что при запуске поиска он заглянет внутрь функции, затем внутри класса, а затем внутри N и найдет там ::N::swap. Поскольку обнаружена потенциальная перегрузка, регулярный поиск не продолжается до внешнего пространства имен ::. Поскольку ::N::swap - это функция, компилятор будет выполнять ADL (зависящий от Argument lookup), но набор связанных пространств имен для основных типов пуст, так что это не приведет к какой-либо другой перегрузке. На этом этапе поиск завершается, и начинается перегрузка. Он попытается сопоставить текущую (одну) перегрузку с вызовом и не сможет найти способ преобразования из int в аргумент ::N::C, и вы получите ошибку.

С другой стороны, использование объявления (using std::swap) предоставляет объявление объекта в текущем контексте (в этом случае внутри самой функции). Поиск немедленно найдет std::swap и прекратит регулярный поиск с помощью ::std::swap и будет использовать его.

Ответ 2

Очевидная причина заключается в том, что использование декларации и использование директивы имеют разные эффекты. Использование декларации вводит имя сразу в текущую область действия, поэтому using std::swap вводит имя в локальную область; поиск останавливается здесь, и единственным символом, который вы найдете, является std::swap. Кроме того, это происходит, когда шаблон определен, поэтому позже объявления в пространстве имен std не найдены. В следующих линии, единственным swap, который будет рассмотрен, является тот, который определенные в <algorithm>, плюс те, которые добавлены ADL (таким образом, один в пространстве имен N). (Но это правда с VС++? Компилятор не реализует корректный поиск имени, поэтому кто знает.)

Использование директивы using указывает, что имена будут отображаться "как если бы" они были объявлены в ближайшем пространстве имен, охватывающих как директивы и назначенного пространства имен; в вашем случае, глобальные Пространство имен. И он фактически не вводит имена; Это просто влияет на поиск имени. Что в случае символ (или всегда, в случае VС++) имеет место при вызове сайт.

Что касается того, почему у вас есть это конкретное сообщение об ошибке: возможно, больше проблема с VС++, поскольку, безусловно, нет не выводимых контекстов в вашем коде. Но нет никаких оснований ожидать двух варианты имеют одинаковое поведение, независимо от компилятора.

Ответ 3

Примечание. Я удалил определение swap в пространстве имен std. Здесь это не актуально. Даже без этого код будет иметь те же проблемы.


Это связано с различиями в правилах между using directive (using namespace std) и using declaration (using std::swap)

Microsoft говорит

Если локальная переменная   имеет то же имя, что и переменная пространства имен, переменная пространства имен   скрытый. Ошибка в переменной пространства имен с тем же именем   как глобальную переменную.

#include<iostream>

namespace T {
    void flunk(int) { std::cout << "T";}
}

namespace V {
    void flunk(int) { std::cout << "V";}
}


int main() {
    using T::flunk;   // makes T::flunk local
    // using V::flunk;  // makes V::flunk local. This will be an error
    using namespace V;  // V::flunk will be hidden
    flunk(1);
}

В соответствии с этим, из-за вашего

template <typename T>
void swap(C<T> & c1, C<T> & c2)

std::swap будет скрыт, если вы используете

using namespace std; 

Таким образом, единственный swap, доступный для вывода шаблона, - N::swap, и он не будет работать для int, потому что он ожидает аргумент template class.

но не, когда

using std::swap;

В этом случае он становится эквивалентным локальному определению . И может использоваться без проблем.