Подтвердить что ты не робот

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

Этот код написан на языке программирования С++ Bjarne Stroustrup (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), а не локальной г (дважды). Из определения "точки создания" следует, что a параметр шаблона никогда не может быть привязан к локальному имени или классу член.

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) уже будет в области видимости.

  • Определение "точки создания" означает, что параметр шаблона никогда не может быть привязан к локальному имени или члену класса. Изменено ли это в С++ 14? Я получаю ошибку с С++ (g++ 4.9.2), но не получаю ошибку с С++ 14 (g++ 4.9.2).

4b9b3361

Ответ 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);
}
  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 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'?