Почему (не относящееся к делу) использование объявления может примирить неоднозначность перегрузки с зависящим от аргумента поиском?


Это продолжение вопроса здесь о перегрузке функций с зависящим от аргумента поиском (ADL). Я хотел проверить свое понимание правил в этих обстоятельствах, поэтому я написал тестовый код.

Во-первых, конечно, в std нет swap для класса HasPtr, поэтому я написал собственное пространство имен, которое содержит версию hasptr swap в дополнение к уже определенной в глобальной области видимости. Объявление using работает так, как я и ожидал-ошибка двусмысленности была произведено потому, что существует версия hasptr swap, уже определенная, как это делается в "C++ Primer", 5ed.

Затем я хочу посмотреть, что произойдет, если я изменю using declaration на using directive. В книге говорится, что компилятор будет молчать, пока функция не будет вызвана на самом деле. Я хочу проверить это, поэтому код выглядит следующим образом:

#include <string>

class HasPtr {
public:
    friend void swap(HasPtr&, HasPtr&);
    std::string *ps;
};

void swap(HasPtr &lhs, HasPtr &rhs) {
    swap(lhs.ps, rhs.ps); // swap the pointers, not the string data
}

namespace myNS {
    void swap(HasPtr &lhs, HasPtr &rhs)     {
        std::string s = "in my name space";
        swap(lhs.ps, rhs.ps); // swap the pointers, not the string data
    }
}

class Foo {
    friend void swap(Foo &lhs, Foo &rhs);
    HasPtr h;
};


void swap(Foo &lhs, Foo &rhs) {
    using std::swap;  //<- commenting this line will cause error
    using namespace myNS;
    swap(lhs.h, rhs.h);
}

int main() {
    Foo f1, f2;
    swap(f1, f2);
}
Странная вещь произошла в строке 27 (using std::swap;). Если я это прокомментировал, то имя myNS:: swap, которое имеет точно такую же подпись, как и то, которое уже определено в глобальный охват был поднят до глобального масштаба и, как я и ожидал, вызвал ошибку перегруженности двусмысленностью.

Но, если я не комментирую строку 27 и не компилирую, никакой ошибки двусмысленности не сообщается. И программа выполняет:: swap, первоначально определенный в глобальной области видимости, как будто директива using using namespace myNS; не поднимает myNS:: swap так, чтобы он не был добавлен в набор кандидатов для перегрузки. Я просто не мог понять этот феномен. Почему можно используя объявления от несущественных имен (std, конечно, не содержит версию hasptr swap) примирить неоднозначность перегрузки под ADL? Почему именно оригинал:: swap, а не его конкурент в myNS, выбран для выполнения? Имеет ли строка 27 какие-либо побочные эффекты к перегрузке процесса (скажем, подавление имен из поднятого пространства имен, чтобы исходные имена имели более высокий приоритет)? Спасибо за ваши ответы.

Проблема может быть воспроизведена в Visual Studio 2015 Update 3 на Windows 7 и в GCC 4.8.4 на ubuntu 14.04, оба 64-битовый.

2 3

2 ответа:

Механика в игре здесь троякая.

  1. A используя объявление, как using std::swap, является декларацией . Он вводит объявление swap в декларативную область функции.

  2. С другой стороны, директива using не вводит объявления в текущую декларативную область. Он позволяет только безоговорочному поиску обрабатывать имена из назначенных пространств имен, , как если бы они были объявлены в ближайшем заключительном пространстве имен текущей декларативной области.

  3. Объявления в более мелких декларативных областях скрывают объявления из более крупных включающих декларативных областей.

По отношению к вышесказанному, способ его настройки выглядит следующим образом:

  1. std::swap объявляется внутри swap(Foo, Foo).
  2. имена внутри myNS становятся доступными для swap(Foo, Foo), как если бы они были объявлены вместе с ним в одном пространстве имен.
  3. в объявление, добавленное в #1, скрывает сделанное видимым в #2.
  4. ::swap может быть найден ADL (несмотря на то, что он также скрыт #1), но myNS::swap не может. поскольку версия myNS и скрыта, и не найдена ADL, она ни с чем не конфликтует.

Когда вы удаляете объявление std::swap, теперь у вас есть myNS::swap видимый. ADL также находит ::swap, давая вам две перегрузки. Они оба являются допустимыми перегрузками и порождают очевидную двусмысленность.

Обратите внимание, что using-directive и using-declaration имеют разный эффект:

(Курсив мой)

Using-directive: с точки зрения безусловного поиска имени любого имени после директивы using и до конца области, в которой оно появляется, каждое имя из ns_name является видимым , как если бы оно было объявлено в ближайшем заключающем пространстве имен, которое содержит как директиву using, так и ns_name.

Это означает для using namespace myNS;, имя myNS::swap видно так, как если бы оно было объявлено в глобальной области видимости. Если using std::swap; прокомментировано, то 2 swap s будут найдены в глобальной области видимости и затем вызовут неоднозначность.

Если using std::swap; раскомментирован, то пространство имен swap из std будет найдено в области действия функции, тогда поиск имени остановится, дальнейшая глобальная область не будет проверяться. Заметим, что глобальные ::swap могут быть найдены ADL, с добавлением std::swap они будут рассмотрены в разрешении перегрузки, и ::swap является выбран тогда без двусмысленности. myNS::swap не будет работать в этом случае.

Поиск имени исследует области, как описано ниже, пока не найдет по крайней мере одно объявление любого вида, после чего поиск останавливается и никакие другие области не рассматриваются.