Ответ 1
"В 1985 году был выпущен первый выпуск The С++ Programming Language, который стал окончательной ссылкой на язык, поскольку еще не был официально установлен. wiki История С++ Так что это не изменилось между С++ 11 и С++ 14. Я могу предположить (и, пожалуйста, возьмите это с солью), она изменилась между" предварительной стандартизацией" и стандартизацией. Может быть, кто-то, кто лучше знает историю С++, может пролить больше света здесь.
Что же происходит на самом деле:
Сначала давайте избежим простого:
extern g(double);
Это недействительно С++. Исторически, к сожалению, C допускал отсутствие типа. В С++ вы должны написать extern void g(double)
.
Далее, пусть игнорирует перегрузку g(double)
, чтобы ответить на ваш первый вопрос:
template <class T>
void f(T value)
{
g(value);
}
void g(int v);
int main()
{
f(2);
}
В С++ существует печально известное имя поиска по двум фазам:
- На первом этапе, при определении шаблона, все не зависящие имена разрешаются. Несоблюдение этого является трудной ошибкой;
- Зависимые имена разрешаются во второй фазе при создании экземпляра шаблона.
Правила немного сложнее, но суть в этом.
g
зависит от параметра шаблона T
, поэтому он проходит первую фазу. Это означает, что если вы никогда не создаете экземпляр f
, код компилируется просто отлично. На второй фазе f
создается T = int
. g(int)
теперь выполняется поиск, но не найден:
17 : error: call to function 'g' that is neither visible in the template definition nor found by argument-dependent lookup g(value); ^ 24 : note: in instantiation of function template specialization 'f<int>' requested here f(2); ^ 20 : note: 'g' should be declared prior to the call site void g(int v);
Для того, чтобы произвольное имя g
прошло с летающими цветами, у нас есть несколько вариантов:
- Объявить
g
ранее:
void g(int);
template <class T>
void f(T value)
{
g(value);
}
- введите
g
с помощьюT
:
template <class T>
void f(T)
{
T::g();
}
struct X {
static void g();
};
int main()
{
X x;
f(x);
}
- Принесите
g
внутрь с помощьюT
через ADL:
template <class T>
void f(T value)
{
g(value);
}
struct X {};
void g(X);
int main()
{
X x;
f(x);
}
Они, конечно, меняют семантику программы. Они предназначены для иллюстрации того, что вы можете и не можете иметь в шаблоне.
Что касается того, почему ADL не находит g(int)
, но находит g(X)
:
§ 3.4.2 Поиск зависимых от аргументов имен [basic.lookup.argdep]
Для каждого типа аргумента T в вызове функции существует набор из нулевых или более связанных пространств имен и набор из нуля или более ассоциированные классы, которые будут рассматриваться [...]:
Если T является фундаментальным типом, его ассоциированные множества пространств имен и классов являются пустыми.
Если T - тип класса (включая объединения), его ассоциированные классы: сам класс; класс которого он является членом, если таковой имеется; а также его прямых и косвенных базовых классов. Его связанные пространства имен пространства имен, членами которого являются ассоциированные классы. [...]
И, наконец, мы доходим до того, почему extern void g(double);
внутри main не найден: прежде всего мы показали, что g(fundamental_type)
найдено iff it объявляется до определения f
. Поэтому давайте сделаем void g(X)
внутри main
. Подходит ли ADL?
template <class T>
void f(T value)
{
g(value);
}
struct X{};
int main()
{
X x;
void g(X);
f(x);
}
Нет. Поскольку он не находится в том же пространстве имен, что и X
(т.е. Глобальное пространство имен), ADL не может его найти.
Доказательство того, что g
не находится в глобальном
int main()
{
void g(X);
X x;
g(x); // OK
::g(x); // ERROR
}
34: ошибка: ни один член с именем 'g' в глобальном пространстве имен; ты имел ввиду просто 'g'?