В какой момент происходит создание шаблона привязки?

Этот код взят из "языка программирования 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
}

Мои вопросы:

  1. Почему должен работать первый код? g() объявляется позже, и я действительно получаю ошибку с G ++ 4.9.2, что g не объявлено в тот момент.

  2. extern g (double) - как это работает? поскольку возвращаемое значение не имеет значения в случае перегрузки функции, то мы можем пропустить его в предварительных объявлениях?

  3. точка создания экземпляра для f () находится непосредственно перед h () - почему? Разве не логично, что он будет создан, когда f(2) называется? Прямо там, где мы это называем, откуда g(double) будет в области уже.

  4. Определение "точки создания экземпляра" подразумевает, что параметр шаблона никогда не может быть связан с локальным именем или членом класса. Изменилось ли это в 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 Чтобы пройти с летающими цветами у нас есть несколько вариантов:

  1. декларировать g ранее:
void g(int);

template <class T>
void f(T value)
{
    g(value);
}
  1. приносить g в с T:
template <class T>
void f(T)
{
    T::g();
}

struct X {
   static void g();
};

int main()
{
    X x;
    f(x);
}
  1. приносить 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]

  1. Для каждого типа аргумента 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"; Вы имели в виду просто "г"?

Другие вопросы по тегам