Подтвердить что ты не робот

Gcc и clang неявно создают экземпляры шаблона во время разрешения перегрузки оператора

Рассмотрим этот код:

struct A; // incomplete type

template<class T>
struct D { T d; };

template <class T>
struct B { int * p = nullptr; };

int main() {
    B<D<A>> u, v;
    u = v;  // doesn't compile; complain that D<A>::d has incomplete type
    u.operator=(v); // compiles
}

Демо. Поскольку u.operator=(v) компилируется, но u = v; не имеет места, где-то во время разрешения перегрузки для последнего выражения компилятор должен иметь неявно созданный экземпляр D<A> - но я не понимаю, почему требуется эта инстанция.

Чтобы сделать вещи более интересными, этот код компилируется:

struct A; // incomplete type

template<class T>
struct D; // undefined

template <class T>
struct B { int * p = nullptr; };

int main() {
    B<D<A>> u, v;
    u = v;
    u.operator=(v);
}

Демо.

Что здесь происходит? Почему u = v; вызывает неявное создание экземпляра D<A> - типа, который нигде не используется в теле определения B - в первом случае, но не во втором?

4b9b3361

Ответ 1

Вся суть вопроса: ADL:

N3797 - [basic.lookup.argdep]

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

следующее:

Для каждого типа аргумента T в вызове функции существует набор из нулевых или более связанных пространств имен и a набор нулевых или более связанных классов. [...] Наборы пространства имен и классы определяются следующим образом:

  • Если T - тип класса [..], его ассоциированные классы:... furthemore, если T является специализацией шаблона класса, его связанные пространства имен и классы также включают: пространства имен и классы, связанные с типы аргументов шаблона, предоставленные для параметров типа шаблона

D<A> является ассоциированным классом и, следовательно, в списке, ожидающем его поворота.

Теперь для интересной части [temp.inst]/1

Если спецификация шаблона класса явно не была создана (14.7.2) или явно специализирована (14.7.3), специализация шаблона класса неявно создается [...] , когда полнота типа класса влияет на семантику программы

Можно подумать, что полнота типа D<A> вообще не влияет на семантику этой программы, однако [basic.lookup.argdep]/4 говорит:

При рассмотрении связанного пространства имен поиск выполняется так же, как и поиск, выполняемый, когда соответствующее пространство имен используется как определитель (3.4.3.2) кроме того:

[...] Любые функции имени пространства имен или шаблоны функций друзей, объявленные в связанных классах, видны в их соответствующих пространства имен, даже если они не видны во время обычного поиска (11.3)

то есть. полнота типа класса фактически влияет на объявления друзей → полнота типа класса влияет на семантику программы. Это также причина, по которой работает ваш второй образец.

TL; DR D<A> создается.

Последний интересный момент касается того, почему ADL запускается в первую очередь для

u = v; // Triggers ADL
u.operator=(v); // Doesn't trigger ADL

В §13.3.1.2/2 указывается, что не может быть нечлена operator= (кроме встроенных). Присоедините это к [over.match.oper]/2:

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

и логический вывод: нет смысла выполнять поиск ADL, если в таблице 11 нет формы, отличной от члена. Однако [temp.inst] p7 расслабляет это:

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

и что причина, по которой clang инициировала весь процесс ADL -> implicit instantiation, в первую очередь.

Начиная с r218330 (на момент написания этого, это было сделано несколько минут назад), это поведение было изменено, чтобы не выполнять ADL для operator= вообще.


Ссылки

Спасибо Ричарду Смиту и Дэвиду Блейки за то, что помогли мне понять это.

Ответ 2

Ну, я думаю, что в Visual Studio 2013 код должен выглядеть (без = nullptr):

  struct A; // incomplete type

  template<class T>
  struct D { T d; };

  template <class T>
  struct B { int * p; };

  int void_main() {
    B<D<A>> u, v;
    u = v;          //  compiles
    u.operator=(v); // compiles
    return 0;
    }

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

Что касается ошибки во время выполнения - переменная v используется без инициализации - она ​​правильная - у структуры B нет конструктора = > B:: p не инициализируется и может содержать мусор.