С++ dynamic_cast для пересылки объявленных компиляторов классов классов, но безопасно ли это?

Следующий код компилирует и дает результат, как можно было бы ожидать (GCC и clang):

template <typename T> struct Derived;

struct Base
{
    template <typename T>
    void foo(T * const t)
    {
        dynamic_cast<Derived<T> * const>(this)->bar(t);
    }
};

template <typename T>
struct Derived : Base
{
    void bar(T const *) const { }
};

Код отправляет вызов foo в Base в bar в Derived.

Как точка ссылки, следующий код не компилируется:

struct Derived2;

struct Base2
{
    template <typename T>
    void foo(T * const t)
    {
        dynamic_cast<Derived2 * const>(this)->bar(t);
    }
};

struct Derived2 : Base2
{
    template <typename T>
    void bar(T const *) const { }
};

GCC предоставляет следующую диагностику:

main.cpp: In member function 'void Base2::foo(T*)':
main.cpp:126:45: error: invalid use of incomplete type 'struct Derived2'
         dynamic_cast<Derived2 * const>(this)->bar(t);
                                             ^
main.cpp:119:8: note: forward declaration of 'struct Derived2'
 struct Derived2;
        ^

Стандартные состояния С++ 14 в разделе о правиле одного определения:

5 Точно одно определение класса требуется в блоке трансляции если класс используется таким образом, чтобы тип класса был полный.
[Пример: следующая полная единица перевода хорошо сформированный, хотя он никогда не определяет X:
struct X;//объявляем X как тип структуры
struct X * x1;//использование X в образовании указателя
X * x2;//использование X в образовании указателя
-end example]
[Примечание: правила для декларации и выражения описывают, в каких контекстах полный класс типы требуются. Тип класса T должен быть полным, если: (5.1) - объект типа T определен (3.1), или
(5.2) - объявлен нестатический член данных типа типа T (9.2) или
(5.3) - T используется как тип объекта или тип элемента массива в новом выражении (5.3.4), или
(5.4) - преобразование lvalue-to-rale применяется к glvalue, относящемуся к объекту типа T (4.1), или (5.5) - выражение преобразуется (неявно или явно) в тип T (п. 4, 5.2.3, 5.2.7, 5.2.9, 5.4) или (5.6) - выражение, которое не является константой нулевого указателя и имеет тип, отличный от cv void *, преобразуется в указатель типа к T или ссылается на T с использованием стандартного преобразования (раздел 4), dynamic_cast (5.2.7 ) или static_cast (5.2.9), или...

Похоже, что первый пример не является законным. Это конструкция плохо сформирована? Если да, то почему я не получаю ошибку?

Ответ 1

РЕДАКТИРОВАТЬ: После небольшого размышления: Шаблоны сначала определяются, если вы их создаете. Таким образом, ваш первый код работает, потому что шаблон сначала определяется, если компилятор достиг линии, в которой вы создаете экземпляр класса шаблона.