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

class P
{
};

template< typename P >
class C : public P
{
  public:
  void f()
  {
    P::f();
  }
};

int main() {
  C<P> c1;
  return 0;
}

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

Однако, если C является шаблоном, тогда это происходит, только если f() фактически вызывается.

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

Ответы

Ответ 1

Собственно, программа, которую вы опубликовали, не плохо сформирована. Хотя "неявное создание экземпляра" C<P> c1; создает экземпляры всех объявлений участников,

специализация члена неявно создается, когда специализация ссылается в контексте, который требует определения члена

[N4140, 14.7.1 (2)] Поскольку вы никогда не используете C<P>::f, его определение никогда не требуется и, следовательно, никогда не создается.

Это отличается от "явного экземпляра", например

template class C<P>;

который должен был бы определить определение всех членов C<P> и, таким образом, привести к ошибке.

Ответ 2

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

Возьмите vector::push_back(const T&), для которого требуется T копировать. Но вы все равно можете использовать большую часть vector, даже если T является подвижным не скопированным типом.

Ответ 3

Внутри класса шаблона идентификатор P является параметром типа шаблона, он не относится к class P, определенному ранее. Поэтому вы не можете сказать, что P::f() не существует, так как вы не знаете, какой класс будет заменен параметром P.

И позже, когда вы вызываете шаблон для объявления переменной c1, функция не нужна, поэтому нет "мертвого кода" - есть только шаблон, который никогда не был расширен до фактического кода.

Ответ 4

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

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

Ответ 5

Переменная C<P> c1; не используется, а функция C::f() никогда не вызывается. Компилятор не создает тело функции до его использования. Это простая оптимизация, которая ускоряет компиляцию.