Этот код взят из «языка программирования C ++» Бьярна Страуструпа (C.13.8.3 Binding of Instantiation Binding)
template <class T>
void f(T value)
{
g(value);
}
void g(int v);
void h()
{
extern g(double);
f(2);
}
И он упоминает:
Здесь точка создания экземпляра для f () находится непосредственно перед h (), поэтому
g (), вызываемая в f (), является глобальным g (int), а не локальным
г (дважды). Определение «точки инстанции» подразумевает, что
Параметр шаблона никогда не может быть связан с локальным именем или классом
член.
void h()
{
struct X {}; // local structure
std::vector<X> v; // error: can't use local structure as template parameter
}
Мои вопросы:
Почему должен работать первый код? g()
объявляется позже, и я действительно получаю ошибку с G ++ 4.9.2, что g
не объявлено в тот момент.
Extern G (двойной) — как это работает? поскольку возвращаемое значение не имеет значения в случае перегрузки функции, то мы можем пропустить его в предварительных объявлениях?
точка создания экземпляра для f () находится непосредственно перед h () — Зачем? Разве не логично, что он будет создан, когда f(2)
называется? Прямо там, где мы это называем, откуда g(double)
будет в области уже.
Определение «точки создания экземпляра» подразумевает, что параметр шаблона никогда не может быть привязан к локальному имени или члену класса — Изменилось ли это в C ++ 14? Я получаю ошибку с C ++ (G ++ 4.9.2), но не получаю ошибку с C ++ 14 (G ++ 4.9.2).
«В 1985 году было выпущено первое издание языка программирования C ++, которое стало окончательной ссылкой на язык, так как еще не было официального стандарта.» Вики C ++ История Так что между C ++ 11 и C ++ 14 ничего не изменилось. Я могу предположить (и, пожалуйста, примите это с небольшим количеством соли), что это изменилось между «предварительной стандартизацией» и стандартизацией. Может быть, кто-то, кто лучше знает историю C ++, может пролить больше света здесь.
Что касается того, что на самом деле происходит:
Сначала давайте уберемся с простого:
extern g(double);
Это неверный C ++. Исторически, к сожалению, C допускал пропуск типа. В C ++ вы должны написать extern void g(double)
,
Далее давайте проигнорируем g(double)
Перегрузка, чтобы ответить на ваш первый вопрос:
template <class T>
void f(T value)
{
g(value);
}
void g(int v);
int main()
{
f(2);
}
В C ++ есть печально известный двухфазный поиск имен:
Правила немного сложнее, но в этом суть.
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);
внутри главное не найдено: прежде всего мы показали, что g(fundamental_type)
найден тогда и только тогда объявлено до 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»; ты имел ввиду
просто г?
Других решений пока нет …