Я немного играю со статическим полиморфизмом, я вызываю функцию, которая внутренне вызывает «правильную» специализированную функцию в зависимости от типа исходного аргумента (в основном я делаю тегирование). Вот код:
#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.
f_helper(typename T::tag_type{})
является зависимым от типа выражением, потому что T::tag_type
это зависимый тип. Это означает, что f_helper
не должен быть видимым, пока f<T>
создается из-за двухфазного поиска.
РЕДАКТИРОВАТЬ: Я почти уверен, что это на самом деле неопределенное поведение. Если мы посмотрим на 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)
объявления функций, найденные в контексте определения шаблона
или контекст экземпляра шаблона найден.Если имя функции
является безусловным идентификатором, и вызов будет неправильно сформирован или найдет
лучшее совпадение — поиск в связанных пространствах имен
рассмотрел все объявления функций с внешними связями
введены в этих пространствах имен во всех единицах перевода, а не только
учитывая эти объявления, найденные в определении шаблона и
контексты экземпляра шаблона, то программа имеет неопределенный
поведение.
Последний абзац для меня указывает, что это неопределенное поведение. function call that depends on a template parameter
здесь f_helper(typename T::tag_type{})
, f_helper
не видно когда f
создается, но это было бы, если бы мы выполнили поиск имени после того, как все модули перевода были скомпилированы.
Я согласен, код плохо сформирован. Я удивлен, что ни g ++, ни clang ++ даже не предупредили об этом.
14.6.2 / 1:
[В выражении формы:
- постфикс-выражение
(
список_выражений [выбирать])
где постфикс-выражение является ID-выражение, ID-выражение обозначает зависимое имя если любое из выражений в список_выражений является зависимым от типа выражением (14.6.2.2) или, если неквалифицированный-ID из ID-выражение это Шаблон-идентификатор в котором любой из аргументов шаблона зависит от параметра шаблона. … Такие имена не связаны и ищутся в точке создания шаблона (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), найдены только объявления функций, найденные либо в контексте определения шаблона, либо в контексте создания шаблона.
Призыв к f_helper(typename T::tag_type{});
зависит от параметра шаблона T
так имя f_helper
не должны быть видны до момента создания f<T>
(из-за двухфазного поиска имени).
Я полагаю, что код работает, потому что реализациям разрешено откладывать момент создания экземпляров шаблонов функций до конца модуля перевода, когда определения для f_helper
доступны.
N3936 §14.6.4.1 / 8 [temp.point]
Специализация для шаблона функции, шаблона функции-члена или функции-члена или статического члена данных шаблона класса может иметь несколько точек создания экземпляров в единице перевода, и в дополнение к описанным выше точкам создания экземпляров, для любой такой специализации, которая имеет точку создания экземпляра в единице перевода, конец единицы перевода также считается точкой создания. Специализация для шаблона класса имеет не более одной точки создания экземпляра в единице перевода. Специализация для любого шаблона может иметь точки создания экземпляров в нескольких единицах перевода. Если две разные точки инстанцирования дают шаблону специализации разные значения в соответствии с одной
Правило определения (3.2), программа некорректна, диагностика не требуется.