Виртуальная точка виртуальной функции С++ CRTP экземпляра

Я пытаюсь понять, является ли обычный шаблон CRTP стандартным.

Код ниже компилируется и работает как ожидается (на clang).

Но мое понимание соответствующих стандартных глав/пунктов гласит, что точка инстанцирования виртуальной функции CRTP < Derived, Base > :: DoSomething() должен быть в точке (B) кода, где полное объявление Derived недоступно. Поэтому внутренний тип typedef также не должен быть доступен.

Может ли кто-нибудь указать соответствующую стандартную главу, которая проверяет этот код?

Другими словами, что-то, что говорит о том, что в этом случае создается виртуальная функция ATFER точка C? Большое спасибо за любую информацию.

Франческо

//-------------------------
// START CODE

#include <iostream>

struct Type1 {};
struct Type2 {};

struct Base
{
  virtual ~Base() {}
  virtual void DoSomething() = 0;
};

template< typename T, typename U >
struct CRTP : U
{
  virtual void DoSomething() { DoSomething( typename T::Type() ); }

 void DoSomething( Type1 ) { std::cout << "1\n"; }
 void DoSomething( Type2 ) { std::cout << "2\n"; }
};

// (A) point of inst. of CRTP< Derived, Base > ( 14.7.1.4 ) ??
// (B) point of inst. of CRTP< Derived, Base >::DoSomething() (14.6.4.1.4 ) ??

struct Derived : CRTP< Derived, Base >
{
  typedef Type2 Type;
};

// (C)

int main()
{
  Base *  ptr = new Derived;
  ptr->DoSomething();
  delete ptr;
}

// END CODE
//-------------------------

Соответствующие (?) стандартные абзацы:

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

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

14.7.1 9 Реализация не должна имплицитно создавать шаблон функции, шаблон-член, не виртуальную функцию-член, класс-член или статический элемент данных шаблона класса, который не требует инсталляции. Неясно, реализует ли реализация неявно экземпляр виртуальной функции-члена шаблона класса, если бы функция виртуального члена не создавалась иначе.

Ответ 1

Это, по-видимому, является результатом того, что компилятор задерживает создание экземпляра CRTP<Derived, Base>::DoSomething() до конца единицы перевода, как это разрешено (см. CWG выпуск 993).

CRTP<Derived, Base> определенно создается непосредственно перед определением Derived (§14.6.4.1 [temp.point]/p4, все кавычки равны N3936):

Для специализации шаблона шаблона шаблон элемента специализация или специализация для члена класса класса шаблон, если специализация неявно создается, потому что она ссылается из другой специализированной специализации, если контекст, с которого ссылается специализация, зависит от параметр шаблона, и если специализация не создается до создания экземпляра шаблона, точка экземпляр непосредственно перед точкой создания охватывающий шаблон. В противном случае точка инстанцирования для такого специализация немедленно предшествует объявлению области пространства имен или определение, относящееся к специализации.

Независимо от того, требуется ли CRTP<Derived, Base>::DoSomething() для создания экземпляра, зависит от значения фразы, указанной в контексте, который требует определения члена (§14.7.1 [temp.inst]/p2). Все нечистые виртуальные функции используются odr (§3.2 [basic.def.odr]/p2), и "каждая программа должна содержать ровно одно определение каждой не-встроенной функции или переменной, которая является odr-используемой в этой программе" (§3.2 [basic.def.odr]/p4); является ли то, что считается "ссылкой в ​​контексте, требующем определения члена", неясно.

(Даже если это не требуется для создания экземпляра, однако, компилятор по-прежнему свободен для его создания в соответствии с §14.7.1 [temp.inst]/p11 - "Не указано, реализует ли реализация неявно экземпляр виртуального члена функции шаблона класса, если бы функция виртуального участника не была бы создана иначе.".)

Если CRTP<Derived, Base>::DoSomething() действительно инстанцируется, то ситуация рассматривается в §14.6.4.1 [temp.point]/p5 и p8 (выделение мое):

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

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

То есть, он имеет две точки инстанцирования, одну сразу после точки CRTP< Derived, Base > инстанцирования и одну в конце единицы перевода. В этом случае в двух точках экземпляров поиск имени для typename T::Type приведет к различным результатам, поэтому программа плохо сформирована, не требуется диагностика.

Ответ 2

Использование new Derived вызывает создание экземпляра класса Derived.

Исправление: Derived не является самим шаблоном, поэтому его структура структуры и содержащиеся объявления участников необходимы сразу. Это вызывает создание CRTP<Derived,Base> сразу после определения Derived. Мне придется искать официальный стандарт позже, когда у меня будет больше времени; но дело в том, что создание CRTP только определяет структуру и доступные члены, а не тела функций-членов; и он знает структуру и члены класса Derived, когда он это делает.

Функции-члены не создаются, пока они не используются (здесь, конструктор), и он уже имеет сам класс в то время. Другое дело, чтобы выяснить, является ли конструктор Derived, поскольку он не является шаблоном, генерируется сразу же после класса или только если/когда это необходимо. Если первое, это можно сделать ленивым, сделав Derived шаблоном с фиктивным аргументом. Но это не влияет на этот конкретный вопрос: независимо от того, правильно ли после Derived или сразу после main, экземпляр функции еще не прошел синтаксический анализ объявления Derived.

Это вызывает создание CRTP<Derived,Base>. Но в обоих случаях требуется только структура класса, а не фактический код для любого из членов. Удалите все тела встроенных функций, и вы увидите, что на данный момент проблем нет.

Теперь используется конструктор Derived по умолчанию, поэтому Derived::Derived() неявно создается. Точка инстанцирования сразу же следует за определением main.

При создании экземпляра Derived::Derived() ему требуется CRTP<Derived,Base>::CRTP(). Он создается в той же точке, что и требуемый экземпляр шаблона. Этот конструктор нуждается во всех виртуальных функциях, поэтому DoSomething() создается, опять же, в той же точке, что и созданная им функция. Вы можете видеть, что все это происходит хорошо после того, как полное определение полностью визуализированного класса Derived известно в терминах всех объявлений всех членов (а не тел функций).

Что недостающее понимание: определение класса не включает определения функции-члена, даже если они указаны в лексической области окружения определения класса. Помните различие между определениями и декларациями отдельно для классов и функций.