Почему при двухэтапном поиске не выбирается перегруженная версия swap?

Я изучаю этот увлекательный ответ на тонкий вопрос относительно наилучшей практики реализации swap функции для определяемых пользователем типов. (Изначально мой вопрос был мотивирован обсуждением незаконности добавления типов в пространство имен std.)

Я не буду повторно распечатывать фрагмент кода из указанного выше ответа.

Вместо этого я хотел бы понять ответ.

В ответе, который я привел выше, под первым фрагментом кода говорится о перегрузке swap в namespace std (а не о специализации в этом пространстве имен):

Если ваш компилятор выводит что-то другое, значит, он неправильно реализует «двухэтапный поиск» для шаблонов.

Далее в ответе указывается, что специализация swap на namespace std (в отличие от его перегрузки) дает другой результат (желаемый результат в случай специализации).

Однако ответ происходит в дополнительном случае: специализация подкачки для определенного пользователем шаблона класса - и в этом случае, опять же, желаемый результат не достигается.

К сожалению, ответ просто констатирует факты; он не объясняет почему.

Может ли кто-нибудь уточнить этот ответ и описать процесс поиска в двух конкретных фрагментах кода, приведенных в этом ответе:

  • перегрузка swap в namespace std для определенного пользователем нешаблонного класса (как в первом фрагменте кода связанного ответа)

  • специализация swap на namespace std для определяемого пользователем класса шаблона (как в последнем фрагменте кода связанного ответа)

В обоих случаях вызывается общий std::swap, а не определяемый пользователем swap. Почему?

(Это прольет свет на природу двухэтапного поиска и причину использования наилучшей практики для реализации определяемого пользователем _13 _; спасибо.)


person Dan Nissenbaum    schedule 27.01.2014    source источник
comment
Однако ответ заключается в дополнительном случае: специализация подкачки для определенного пользователем класса шаблонов - и в этом случае, опять же, желаемый результат не достигается. - Вы уверены, что специализируетесь? Мне кажется, что это снова перегрузка, и именно поэтому она не сработает (перегруженная функция не находилась в области, где был вызван swap, и ADL не находит ее, потому что нет связанного пространства имен std для запуска поиска в это пространство имен). (Это слишком кратко, чтобы быть полным ответом, но может помочь вам начать понимание.)   -  person    schedule 27.01.2014
comment
Что касается: перегрузки swap в namespace std: это неопределенное поведение. Первый фрагмент кода связанного ответа исследует, что произошло бы, если он определил поведение и действовал так же, как и любой другой namespace, используя другой namespace (exp для экспериментального).   -  person Yakk - Adam Nevraumont    schedule 27.01.2014
comment
@Yakk - понял. Здесь предполагается, что можно определить дополнительные типы в namespace std (хотя это не так - по причинам, не связанным с вопросом). Я мог бы использовать namespace exp в своем вопросе, но почему-то думаю, что это не привлечет такого же (надеюсь, должного) внимания.   -  person Dan Nissenbaum    schedule 27.01.2014
comment
@hvd Хороший вопрос. Как можно специализироваться swap на классе шаблона, а не на перегрузке? Это вообще возможно?   -  person Dan Nissenbaum    schedule 27.01.2014
comment
@DanNissenbaum Частичная специализация функций невозможна, если только C ++ 11 не изменил это (а, насколько мне известно, этого не произошло).   -  person    schedule 27.01.2014
comment
@hvd - А как насчет полной специализации swap, но ее специализации для класса template?   -  person Dan Nissenbaum    schedule 27.01.2014
comment
@hvd Относительно вашего первоначального комментария - я бы подумал, что exp::algorithm не будет создан компилятором до тех пор, пока не будет скомпилирована функция main, в это время перегруженная версия swap находится - в чем я ошибаюсь? Спасибо.   -  person Dan Nissenbaum    schedule 27.01.2014
comment
@Shahbaz Я только что прочитал. Очаровательный. Я до сих пор не понимаю, принят ли текущий принятый подход, указанный в stackoverflow.com/a/2684544/368896, означает принятие предложения по предоставленной вами ссылке.   -  person Dan Nissenbaum    schedule 27.01.2014


Ответы (2)


Преамбула с большим количеством стандартного языка

Вызов swap() в этом примере влечет за собой зависимое имя, потому что его аргументы begin[0] и begin[1] зависят от параметра шаблона T окружающего algorithm() шаблона функции. Двухэтапный поиск таких зависимых имен определяется в Стандарте следующим образом:

14.6.4.2 Функции-кандидаты [temp.dep.candidate]

1 Для вызова функции, где постфиксное выражение является зависимым именем, функции-кандидаты находятся с использованием обычных правил поиска (3.4.1, 3.4.2), за исключением того, что:

- Для части поиска, использующей поиск по некорректному имени (3.4.1), обнаруживаются только объявления функций из контекста определения шаблона.

- Для части поиска с использованием связанных пространств имен (3.4.2) обнаруживаются только объявления функций, найденные либо в контексте определения шаблона, либо в контексте создания экземпляра шаблона.

Неквалифицированный поиск определяется

3.4.1 Неправильный поиск имени [basic.lookup.unqual]

1 Во всех случаях, перечисленных в п. 3.4.1, в областях производится поиск декларации в порядке, указанном в каждой из соответствующих категорий; поиск имени заканчивается, как только будет найдено объявление для имени. Если объявление не найдено, программа имеет неправильный формат.

и поиск, зависящий от аргументов (ADL) как

3.4.2 Поиск имени в зависимости от аргумента [basic.lookup.argdep]

1 Когда постфиксное-выражение в вызове функции (5.2.2) является неквалифицированным-идентификатором, можно искать другие пространства имен, не учитываемые при обычном неквалифицированном поиске (3.4.1), и в этих пространствах имен могут быть найдены объявления дружественной функции области пространства имен или шаблона функции (11.3), не видимые иным образом. Эти изменения в поиске зависят от типов аргументов (а для аргументов шаблона шаблона - пространства имен аргумента шаблона).

Применение Стандарта к примеру

Первый пример вызывает exp::swap(). Это не зависимое имя и не требует двухэтапного поиска имени. Поскольку вызов swap квалифицирован, выполняется обычный поиск, который находит только общий шаблон функции swap(T&, T&).

Второй пример (то, что @HowardHinnant называет «современным решением») вызывает swap(), а также имеет перегрузку swap(A&, A&) в том же пространстве имен, что и где находится class A (в данном случае глобальное пространство имен). Поскольку вызов подкачки неквалифицирован, и обычный поиск, и ADL имеют место в точке определения (опять же, только поиск общего swap(T&, T&)), но другой ADL имеет место в точке создания экземпляра (то есть там, где exp::algorithm() вызывается в main()), и это поднимает swap(A&, A&), что лучше соответствует разрешению перегрузки.

Все идет нормально. Теперь переход на бис: третий пример вызывает swap() и имеет специализацию template<> swap(A&, A&) внутри namespace exp. Поиск такой же, как во втором примере, но теперь ADL не выбирает специализацию шаблона, потому что он не находится в связанном пространстве имен class A. Однако даже несмотря на то, что специализация template<> swap(A&, A&) не играет роли при разрешении перегрузки, она все равно создается в точке использования.

Наконец, четвертый пример вызывает swap() и содержит перегрузку template<class T> swap(A<T>&, A<T>&) внутри namespace exp для template<class T> class A, живущего в глобальном пространстве имен. Поиск такой же, как в третьем примере, и снова ADL не принимает перегрузку swap(A<T>&, A<T>&), потому что она не находится в связанном пространстве имен шаблона класса A<T>. И в этом случае также нет специализации, которая должна быть создана в точке использования, поэтому здесь вызывается общий swap(T&, T&).

Вывод

Даже если вам не разрешено добавлять новые перегрузки к namespace std и только явные специализации, это даже не сработает из-за различных сложностей двухэтапного поиска имени.

person TemplateRex    schedule 27.01.2014
comment
Второй пример, который вы имеете в виду, связан с реализацией swap для класса шаблона? (Похоже, ваш ответ относится к функции void swap(A&, A&) {} global, которая находится в середине связанного ответа - мой второй пункт касается определения template <class T> void swap(A<T>&, A<T>&) {} в namespace exp , который находится ближе к концу связанного ответа.) - person Dan Nissenbaum; 27.01.2014
comment
Спасибо. Что касается первого случая (где ADL отключен) - ADL может быть отключен, но функция exp::algorithm создается только экземпляром компилятором, когда main функция компилируется, верно? - в этом случае я бы подумал, что перегруженная версия swap будет в области действия. (Где я ошибаюсь?) - person Dan Nissenbaum; 27.01.2014
comment
@DanNissenbaum придется переписать это, удалив до дальнейшего уведомления. - person TemplateRex; 27.01.2014
comment
@DanNissenbaum Я думаю, что объяснение завершено, см. Обновленный ответ. - person TemplateRex; 27.01.2014
comment
Отличный ответ. Спасибо. - person Dan Nissenbaum; 28.01.2014
comment
@dyp Я бы так предположил ... Рассмотрим этот пример: namespace exmpl { template<typename T> class A { public: A(T&& t_) : t(t_) {} private: T t; }; }. В этом примере я бы подумал, что exmpl::A - зависимое имя (зависит от T). Это звучит правильно? - person Dan Nissenbaum; 28.01.2014
comment
@DanNissenbaum exmpl::A сам по себе является id-выражением, имя A в нем не зависит от параметров шаблона в какой-либо форме (именует сам шаблон). Думаю, это не зависимо. Изменить: где exmpl::A появляется в вашем примере? Или вы имеете в виду A без exmpl, внутри шаблона класса? - person dyp; 28.01.2014
comment
@dyp oops, exp::swap действительно не зависит, хотя в этом случае это не имеет значения для поиска. Tnx и обновился! - person TemplateRex; 28.01.2014

Невозможно перегрузить swap в namespace std для определенного пользователем типа. Введение перегрузка (в отличие от специализации) в namespace std - это неопределенное поведение (недопустимо по стандарту, диагностика не требуется).

Невозможно специализировать функцию в целом для класса template (в отличие от класса template экземпляра, т. Е. std::vector<int> - это экземпляр, а std::vector<T> - весь template класс). То, что кажется специализацией, на самом деле является перегрузкой. Итак, применяется первый абзац.

Лучшая практика для реализации определяемого пользователем swap - ввести swap функцию или перегрузку в том же пространстве имен, в котором находится ваш template или class.

Затем, если swap вызывается в правильном контексте (using std::swap; swap(a,b);), как это называется в std библиотеке, сработает ADL, и ваша перегрузка будет обнаружена.

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

В общем, специализация функций крайне хрупка, и вам лучше вводить переопределения. Поскольку вы не можете вводить переопределения в std, единственное место, где они будут надежно найдены, - это ваш собственный namespace. Такие переопределения в вашем собственном пространстве имен также являются предпочтительными переопределениями в std.

Есть два способа добавить swap в ваше пространство имен. Оба работают для этой цели:

namespace test {
  struct A {};
  struct B {};
  void swap(A&, A&) { std::cout << "swap(A&,A&)\n"; }
  struct C {
    friend void swap(C&, C&) { std::cout << "swap(C&, C&)\n"; }
  };

  void bob() {
    using std::swap;
    test::A a, b;
    swap(a,b);
    test::B x, y;
    swap(x, y);
    C u, v;
    swap(u, v);
  }
}

void foo() {
  using std::swap;
  test::A a, b;
  swap(a,b);
  test::B x, y;
  swap(x, y);
  test::C u, v;
  swap(u, v);

  test::bob();
}
int main() {
  foo();
  return 0;
}

первый - ввести его напрямую в namespace, второй - включить его как встроенный friend. Встроенный friend для «внешних операторов» является распространенным шаблоном, который в основном означает, что вы можете найти swap только через ADL, но в этом конкретном контексте ничего не добавляет.

person Yakk - Adam Nevraumont    schedule 27.01.2014
comment
Это отличный ответ. Спасибо. Однако он не напрямую отвечает на мой вопрос (если я не упускаю что-то очевидное). - person Dan Nissenbaum; 27.01.2014
comment
@DanNissenbaum Ваш вопрос состоит из двух частей. Один касается переопределения функций в пространстве имен std. Как я отмечал выше, это неопределенное поведение. Вторая - о (частичной) специализации функций в пространстве имен std. В C ++ это невозможно, частичных специализаций функций нет. Синтаксис, который выглядит так, как будто это частичная специализация, на самом деле переопределяет, что приводит к неопределенному поведению. Вопрос, который вы, возможно, хотели задать, касался namespace exp? В таком случае я бы задала другой вопрос и вообще не упомянула std. - person Yakk - Adam Nevraumont; 27.01.2014
comment
Пожалуйста, позвольте мне притвориться, что разрешено перегружать функции в namespace std (потому что причина, по которой это запрещено, не имеет отношения к моему вопросу, я думаю) - вместо этого представьте, что мой вопрос был оформлен с использованием namespace exp, как в связанный ответ, но в остальном это идентичный вопрос. Я использовал namespace std только для того, чтобы привлечь внимание к вопросу. Спасибо! - person Dan Nissenbaum; 27.01.2014
comment
@Yakk см. Мой ответ, почему это не сработает, даже если это разрешено (скажем, в namespace exp, как в связанном ответе Хиннанта). - person TemplateRex; 27.01.2014