Не предварительно объявленный вызов функции работает для типов классов, но не для примитивных типов
В следующем коде
template <typename T>
void foo(T) {
bar(T{});
}
class Something {};
void bar(Something) {}
int main() {
foo(Something{});
}
(https://wandbox.org/permlink/l2hxdZofLjZUoH4q)
Когда мы вызываем foo()
с параметром Something
, все работает так, как ожидалось, вызов отправляется на пересылку bar(Something)
.
Но когда я изменяю аргумент на целое число и предоставляю перегрузку bar(int)
, я получаю сообщение об ошибке
template <typename T>
void foo(T) {
bar(T{});
}
void bar(int) {}
int main() {
foo(int{});
}
Ошибка:
error: call to function 'bar' that is neither visible in the template definition nor found by argument-dependent lookup
(https://wandbox.org/permlink/GI6wGlJYxGO4svEI)
В случае класса я не определил bar()
в пространстве имен вместе с определением Something
. Это означает, что я не получаю ADL. Тогда почему код работает с типами классов?
Ответы
Ответ 1
Тогда почему код работает с типами классов?
Согласно §6.4.2/2.1:
Наборы пространств имен и классов определяются следующим образом:
- Если T является фундаментальным типом, его ассоциированные множества пространств имен и классов являются пустыми.
Поэтому при написании foo(int)
компилятор будет иметь пустой набор пространств имен и классов, которые будут рассмотрены. Таким образом, вызов bar
должен завершиться неудачей, поскольку он еще не объявлен. Если вы заранее объявите foo(int)
, ваш код будет скомпилирован:
void bar(int);
template <typename T>
void foo(T) {
bar(T{});
}
void bar(int) {}
int main() {
foo(int{});
}
С другой стороны, в случае foo(Something)
, глобальное пространство имен будет частью поиска, поэтому компилятор активно сканирует пространство имен для функции с именем bar
которая может быть вызвана с экземплярами Something
.
Ответ 2
Внутри определения foo
, bar
является зависимым именем, потому что оно вызывается с аргументом, который зависит от параметра шаблона (T
).
Зависимое разрешение имен выполняется дважды [temp.dep.res]:
При разрешении зависимых имен учитываются имена из следующих источников:
-
Объявления, которые видны в точке определения шаблона.
-
Объявления из пространств имен, связанных с типами аргументов функции как из контекста создания ([temp.point]), так и из контекста определения. Bellow, комментарии показывают, где точка инстанцирования:
template <typename T>
void foo(T) { //point of definition of foo
bar(T{});
}
class Something {};
void bar(Something) {}
void bar(int) {}
int main() {
foo(int{});
foo(Something{});
}
//point of instantiation of foo<int>
//point of instantiation of foo<Something>
Для foo<Something>
и foo<int>
никакая bar
не видна с точки определения.
Для foo<Something>
, Something
то, являющееся классом, связано с ним пространством имен, в котором оно объявлено: глобальное пространство имен. Согласно второму пулю, поиск имени выполняется в глобальном пространстве имен с момента создания экземпляра и отображается bar(Something)
.
Для foo<int>
int
является фундаментальным типом и не имеет никаких связанных пространств имен. Таким образом, поиск имени не выполняется с точки инстанцирования, поэтому bar<int>
не найден.