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

Этот код взят из "языка программирования C ++" Бьярна Страуструпа (C.13.8.3 Point 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).


person user1289    schedule 10.10.2016    source источник
comment
В 1985 году было выпущено первое издание языка программирования C ++, которое стало окончательным справочником по языку, поскольку еще не было официального стандарта. wiki Таким образом, это не изменилось между C++11 и C++14. Он изменился между предварительной стандартизацией и стандартизацией.   -  person bolov    schedule 10.10.2016
comment
Проверьте правила в пункте 14.6.4.1 [temp.point].   -  person AndyG    schedule 10.10.2016
comment
также поиск двухфазного поиска имени   -  person bolov    schedule 10.10.2016
comment
@bolov Читаю третье издание (1997).   -  person user1289    schedule 10.10.2016


Ответы (1)


«В 1985 году было выпущено первое издание языка программирования C ++, которое стало окончательным справочником по языку, поскольку еще не было официального стандарта». wiki История C ++ Таким образом, между C ++ 11 и С ++ 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); внутри main не найден: прежде всего мы показали, что g(fundamental_type) найден iff он объявлен до определения f. Так что давайте сделаем это void g(X) внутри main. АДЛ его находит?

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"?

person bolov    schedule 10.10.2016
comment
Спасибо за ответ. У меня два вопроса. 1. Почему в первом примере g является независимым? Это зависит от T (в данном случае int), с ADL его можно найти в пространстве имен, которое определено (для встроенных типов пространство имен должно быть глобальным пространством имен, насколько мне известно). 2. По вашей логике, почему f(x) работает, а f(2) не работает с ADL? - person user1289; 10.10.2016
comment
@ user1289: f(2) не работает с ADL, потому что 2 - это int, который является фундаментальным типом, и 1) Для аргументов фундаментального типа связанный набор пространств имен и классов пуст (см. 3.9.1 Основные типы [basic.fundamental ]) - person AndyG; 10.10.2016
comment
@AndyG Связывание зависимых имен осуществляется путем просмотра имен в пространстве имен аргумента зависимого вызова (глобальные функции рассматриваются в пространстве имен встроенных типов - это из той же книги. - person user1289; 10.10.2016
comment
@ user1289: Но int даже не использует глобальные функции. См. Часть Болова об объявлении g перед шаблоном. Кроме того, я думаю, вам следует прочитать более свежую книгу. Вы задаете вопросы C ++ 14, используя книгу, которая была опубликована до стандарта C ++ 03. - person AndyG; 10.10.2016
comment
Я не нашел. Для аргументов фундаментального типа связанный набор пространств имен и классов в документации пуст. Хотя, как вы упомянули, f (2) не компилируется для меня, а f (x) компилируется. - person user1289; 10.10.2016
comment
@ user1289 ты был прав. g зависит. Но насколько я могу судить, ADL работает только с пользовательскими типами. - person bolov; 10.10.2016
comment
@ user1289 нашел и обновил ADL, что, по сути, и сказал AngyG. - person bolov; 10.10.2016