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

Почему нет необходимости в форвардной декларации в статической диспетчеризации через шаблоны?

Я немного играю со статическим полиморфизмом, я вызываю функцию, которая внутренне вызывает "правильную" специализированную функцию в зависимости от типа исходного аргумента (в основном я делаю тегирование). Вот код:

#include <iostream>

using namespace std;

// tags
struct tag1{}; 
struct tag2{}; 

// the compliant types, all should typedef tag_type
struct my_type1
{
    using tag_type = tag1;
};
struct my_type2
{
    using tag_type = tag2;
};

// static dispatch via tagging
template <typename T>
void f(T) 
{
    cout << "In void f<typename T>(T)" << endl;

    // why can I call f_helper without forward definition?!?        
    f_helper(typename T::tag_type{}); 
}

int main()
{
    my_type1 type1;
    my_type2 type2;

    // how does f below knows about f_helper ?!?!
    // even after instantiation f_helper shouldn't be visible!

    f(type1); 
    f(type2);
}

// helper functions
void f_helper(tag1) 
{
    cout << "f called with my_type1" << endl;
}
void f_helper(tag2)
{
    cout << "f called with my_type2" << endl;
}

Итак, f(T) вызывается с параметром my_type1 или my_type2, который внутренне должен typedef tag_type с соответствующим тегом tag1/tag2. В зависимости от этого внутреннего tag_type, тогда вызывается "правая" оболочка, и это решение делается, конечно, во время компиляции. Теперь я действительно не понимаю, почему этот код работает? Почему нам не нужно пересылать-объявлять f_helper? У меня сначала были обертки, определенные до main (и после f), и я, хотя это нормально, имеет смысл, вам не нужно пересылать объявление, потому что компилятор создает экземпляр шаблона только при вызове f(type1);main()), прежде чем он не знает тип T, поэтому во время создания экземпляра компилятор знает f_wrapper.

Но, как вы видите, даже если я объявляю обертку ПОСЛЕ main(), код все еще работает. Почему это происходит? Я думаю, вопрос немного странный, спрашивая, почему работает код:)


ИЗМЕНИТЬ

Код продолжает компилироваться даже в gcc5 и gcc HEAD 6.0.0.

4b9b3361

Ответ 1

f_helper(typename T::tag_type{}) является зависимым от типа выражением, потому что T::tag_type является зависимым типом. Это означает, что f_helper не должен отображаться до тех пор, пока f<T> не будет создан из-за двухфазного поиска.

EDIT: Я уверен, что это на самом деле поведение undefined. Если мы посмотрим на 14.6.4.2 [temp.dep.candidate], мы увидим этот отрывок:

Для вызова функции, который зависит от параметра шаблона, функции-кандидата найдены с использованием обычных правил поиска (3.4.1, 3.4.2, 3.4.3), за исключением того, что:

- Для части поиска, использующей поиск неквалифицированного имени (3.4.1) или квалифицированный поиск имени (3.4.3), только обнаружены объявления функций из контекста определения шаблона.

- Для части поиска с использованием связанных пространств имен (3.4.2), только объявления функций, найденные в контексте определения шаблона или контекст экземпляра шаблона.

Если имя функции является неквалифицированным идентификатором, и звонок будет плохо сформирован или найдет лучшее совпадение имеет поиск в соответствующих пространствах имен рассмотрены все объявления функций с внешней связью введенные в эти пространства имен во всех единицах перевода, а не только учитывая те декларации, которые были найдены в определении шаблона и контекста экземпляра шаблона, тогда программа имеет undefinedповедение.

Последний абзац для меня указывает, что это поведение undefined. function call that depends on a template parameter здесь f_helper(typename T::tag_type{}). f_helper не отображается при создании f, но это было бы, если бы мы выполнили поиск имени после того, как все единицы перевода были скомпилированы.

Ответ 2

Я согласен, код плохо сформирован. Я удивлен, что ни g++, ни clang++ даже не предупреждают об этом.

14.6.2/1:

В выражении вида:

  • постфикс-выражение ( выражение-список [opt] )

где постфиксное выражение является id-выражением, id-выражение обозначает зависимое имя, если любое из выражений в списке выражений является зависимым от типа выражением (14.6.2.2) или если неквалифицированный идентификатор id-expression - это идентификатор шаблона, в котором любой из аргументов шаблона зависит от параметра шаблона.... Такие имена несвязаны и рассматриваются в момент создания экземпляра шаблона (14.6.4.1) как в контексте определения шаблона, так и в контексте точки создания экземпляра.

[f_helper - постфиксное выражение и id-выражение, а typename T::tag_type{} зависит от типа, поэтому f_helper является зависимым именем.]

14.6.4/1:

При разрешении зависимых имен учитываются имена из следующих источников:

  • Объявления, которые видны в точке определения шаблона.

  • Объявления из пространств имен, связанных с типами аргументов функции как из контекста создания (14.6.4.1), так и из контекста определения.

14.6.4.1/6:

Контекст создания выражения, который зависит от аргументов шаблона, представляет собой набор объявлений с внешней связью, объявленных до момента создания специализированной специализации в той же самой единицы перевода.

14.6.4.2/1:

Для вызова функции, который зависит от параметра шаблона, функции-кандидаты найдены с использованием обычных правил поиска (3.4.1, 3.4.2, 3.4.3), за исключением того, что:

  • Для части поиска, использующей поиск неквалифицированного имени (3.4.1) или поиск квалифицированного имени (3.4.3), найдены только объявления функций из контекста определения шаблона.

  • Для части поиска с использованием связанных пространств имен (3.4.2) найдены только объявления функций, найденные либо в контексте определения шаблона, либо в контексте экземпляра шаблона.

Ответ 3

Вызов f_helper(typename T::tag_type{}); зависит от параметра шаблона T, поэтому имя f_helper не должно быть видимым до момента создания экземпляра f<T> (из-за двухфазного поиска).

Я считаю, что код работает, потому что реализациям разрешено задерживать точку создания шаблонов функций до конца единицы перевода, и в это время доступны определения для f_helper.

N3936 §14.6.4.1/8 [temp.point]

Специализация шаблона функции, шаблона функции-члена или функции-члена или статического элемента данных шаблона класса может иметь несколько точек инстанцирования внутри единицы перевода и в дополнение к моментам создания, описанным выше, для любой такой специализации, которая имеет точку инстанцирования внутри единицы перевода, конец единицы перевода также считается точкой инстанцирования. Специализация шаблона класса имеет не более одной точки инстанцирования внутри единицы перевода. Специализация для любого шаблона может иметь точки инстанцирования в нескольких единицах перевода. Если две разные точки инстанцирования дают шаблонную специализацию разных значений в соответствии с одной (3.2), программа плохо сформирована, не требуется диагностика.