Составление шаблона двухфазной функции: не * только * ADL используется во 2-й фазе? - PullRequest
4 голосов
/ 11 ноября 2019

Мне интересно, почему следующий код компилируется.

#include <iostream>

template<class T> 
void print(T t) {
    std::cout << t;
}

namespace ns {
    struct A {};
}

std::ostream& operator<<(std::ostream& out, ns::A) {
    return out << "hi!";
}

int main() {
    print(ns::A{}); 
}

У меня сложилось впечатление, что в момент создания экземпляра безусловные зависимые имена просматриваются только по ADL - который не должен учитывать глобальное пространство имен. Я не прав?

1 Ответ

2 голосов
/ 12 ноября 2019

Это интересный случай. Работа поиска имени в том виде, как вы их описываете, кратко изложена здесь:

[temp.dep.candidate] (выделено мной)

1 Для вызова функции , где постфиксное выражение является зависимым именем, функции-кандидаты находятся с использованием обычных правил поиска ([basic.lookup.unqual], [basic.lookup.argdep]), за исключениемчто:

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

  • Длячасть поиска с использованием связанных пространств имен ([basic.lookup.argdep]), найдены только объявления функций, найденные либо в контексте определения шаблона, либо в контексте создания шаблона.

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

Бит, который я выделил, - суть вопроса. Описание для "только ADL" предназначено для вызовов функций из foo(bar)! В нем не упоминаются звонки, вызванные перегруженным оператором. Мы знаем, что вызов перегруженных операторов эквивалентен вызову функции, но в параграфе говорится о выражениях в определенной форме, а не только о вызове функции.

Если нужно было изменить шаблон вашей функции на

template<class T> 
void print(T t) {
    return operator<< (std::cout, t);
}

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

Так это ошибка? Я бы сказал, что это так. Намерение, безусловно, состоит в том, чтобы перегруженные операторы находились как именованные функции (даже если они вызваны из соответствующей формы выражения). Так что GCC нужно исправить. Но в стандарте также может использоваться незначительное уточнение формулировки.

...