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

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

#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.

15

Решение

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 создается, но это было бы, если бы мы выполнили поиск имени после того, как все модули перевода были скомпилированы.

9

Другие решения

Я согласен, код плохо сформирован. Я удивлен, что ни 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), найдены только объявления функций, найденные либо в контексте определения шаблона, либо в контексте создания шаблона.

4

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

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

N3936 §14.6.4.1 / 8 [temp.point]

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

3
По вопросам рекламы [email protected]