Виртуальная точка виртуальной функции С++ 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 известно в терминах всех объявлений всех членов (а не тел функций).
Что недостающее понимание: определение класса не включает определения функции-члена, даже если они указаны в лексической области окружения определения класса. Помните различие между определениями и декларациями отдельно для классов и функций.