В какой момент происходит создание шаблона привязки?
Этот код взят из "языка программирования 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), а не локальным g (double). Определение "точки создания экземпляра" подразумевает, что параметр шаблона никогда не может быть привязан к локальному имени или члену класса.
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 (double) - как это работает? поскольку возвращаемое значение не имеет значения в случае перегрузки функции, то мы можем пропустить его в предварительных объявлениях?
точка создания экземпляра для f () находится непосредственно перед h () - почему? Разве не логично, что он будет создан, когда
f(2)
называется? Прямо там, где мы это называем, откудаg(double)
будет в области уже.Определение "точки создания экземпляра" подразумевает, что параметр шаблона никогда не может быть связан с локальным именем или членом класса. Изменилось ли это в C++14? Я получаю ошибку с C++(G++ 4.9.2), но не получаю ошибку с C++14(G++ 4.9.2).
1 ответ
"В 1985 году было выпущено первое издание языка программирования C++, которое стало окончательным эталоном для языка, поскольку еще не было официального стандарта". Вики C++ History Так что между 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"; Вы имели в виду просто "г"?