Не предварительно объявленный вызов функции работает для типов классов, но не для примитивных типов

В следующем коде

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> не найден.