Почему шаблоны функций, созданные (неявно) могут использовать необъявленные символы?
У меня есть следующий код:
template <typename T>
void fun(T t) {
// foo and bar are not declared yet, but this is okay,
// because they can be found through ADL for a class type T
foo(t);
bar(t);
}
struct A {};
void foo(A);
// implicitly instantiate fun<A>(A), with the point of instantiation being after call_fun
void call_fun() {
fun(A{});
}
/* implicit instantiation should be here:
template void fun<A>(A t) {
foo(t); // OK, foo has been declared
bar(t); // NOT OK, bar has not been declared yet
}
*/
// uncommenting the following explicit instantiation makes the code ill-formed
// template void fun(A);
void bar(A);
Здесь есть несоответствие для clang, которое я не понимаю:
- явная реализация невозможного вызова, поскольку она еще не объявлена
- неявное создание экземпляра в том же месте может
GCC и MSVC также компилируются с явным созданием экземпляра, только clang отклоняет его. Однако я не уверен, что компиляция любой версии разрешена стандартом:
Для специализации шаблона функции, специализации шаблона функции-члена или специализации для функции-члена или статического члена данных шаблона класса, если специализация создается неявно, поскольку на нее ссылаются из другой специализации шаблона и контекста, из которого она создается. ссылка зависит от параметра шаблона, точкой создания экземпляра специализации является точка создания охватывающей специализации. В противном случае точка создания экземпляра такой специализации следует сразу за объявлением или определением области пространства имен, которое ссылается на специализацию.
Какой компилятор прав? Неужели все они не соответствуют требованиям?
1 ответ
Специализация для шаблона функции, шаблона функции-члена или функции-члена или члена статических данных шаблона класса может иметь несколько точек создания экземпляров внутри единицы перевода, и в дополнение к точкам создания экземпляров, описанным выше,
- для любой такой специализации, которая имеет точку создания экземпляра в декларации-seq единицы трансляции, до фрагмента частного модуля (если таковая имеется), точка после декларации-seq единицы трансляции также считается точка создания экземпляра и
- для любой такой специализации, которая имеет точку создания экземпляра внутри фрагмента частного модуля, конец единицы перевода также считается точкой создания экземпляра.
Специализация шаблона класса имеет не более одной точки реализации в единице перевода. Специализация для любого шаблона может иметь точки реализации в нескольких единицах перевода. Если две разные точки создания экземпляра придают специализации шаблона разные значения в соответствии с правилом одного определения, программа является неправильной и никакой диагностики не требуется.
В конце единицы перевода есть вторая точка создания экземпляра (у вас нет фрагмента частного модуля, второй пункт не применяется).
Если бы существовала разница между созданием шаблона функции в любой точке создания экземпляра, программа была бы неправильно сформированным отчетом о недоставке, поэтому компилятор волен выбирать любую точку, не проверяя другую.
Clang предпочитает создавать неявные экземпляры в последней точке создания экземпляра, в конце единицы перевода (что приводит к поиску). Он также предпочитает создавать экземпляры явной специализации непосредственно в первой точке создания экземпляра (в отличие от GCC и MSVC), что приводит к тому, что он не находитbar(A)
.
У вас есть неправильно сформированная программа NDR, поскольку использование точки создания экземпляра непосредственно после неявного или явного создания экземпляра и точки создания экземпляра в конце единицы трансляции имеют разные значения.