Почему шаблоны функций, созданные (неявно) могут использовать необъявленные символы?

У меня есть следующий код:

      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

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

Какой компилятор прав? Неужели все они не соответствуют требованиям?

1 ответ

[темп.точка]p7:

Специализация для шаблона функции, шаблона функции-члена или функции-члена или члена статических данных шаблона класса может иметь несколько точек создания экземпляров внутри единицы перевода, и в дополнение к точкам создания экземпляров, описанным выше,

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

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

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

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

Clang предпочитает создавать неявные экземпляры в последней точке создания экземпляра, в конце единицы перевода (что приводит к поиску). Он также предпочитает создавать экземпляры явной специализации непосредственно в первой точке создания экземпляра (в отличие от GCC и MSVC), что приводит к тому, что он не находитbar(A).

У вас есть неправильно сформированная программа NDR, поскольку использование точки создания экземпляра непосредственно после неявного или явного создания экземпляра и точки создания экземпляра в конце единицы трансляции имеют разные значения.

Другие вопросы по тегам