11

The following code compiles and gives result as one would expect in (GCC and 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 { }
};

The code dispatches a call to foo in Base to bar in Derived.

As a point of reference, the following code does not compile:

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 provides the following diagnostic:

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;
        ^

The C++14 standard states in the section on the One Definition Rule, the following:

5 Exactly one definition of a class is required in a translation unit if the class is used in a way that requires the class type to be complete.
[ Example: the following complete translation unit is well-formed, even though it never defines X:
struct X; // declare X as a struct type
struct X* x1; // use X in pointer formation
X* x2; // use X in pointer formation
—end example ]
[ Note: The rules for declarations and expressions describe in which contexts complete class types are required. A class type T must be complete if: (5.1) — an object of type T is defined (3.1), or
(5.2) — a non-static class data member of type T is declared (9.2), or
(5.3) — T is used as the object type or array element type in a new-expression (5.3.4), or
(5.4) — an lvalue-to-rvalue conversion is applied to a glvalue referring to an object of type T (4.1), or
(5.5) — an expression is converted (either implicitly or explicitly) to type T (Clause 4, 5.2.3, 5.2.7, 5.2.9, 5.4), or
(5.6) — an expression that is not a null pointer constant, and has type other than cv void*, is converted to the type pointer to T or reference to T using a standard conversion (Clause 4), a dynamic_cast (5.2.7) or a static_cast (5.2.9), or ...

This seems to state that the first example is not legal. This is construct ill-formed? If so, why am I not getting an error?

ThomasMcLeod
  • 7,603
  • 4
  • 42
  • 80
  • 6
    For dependent names, the completeness is measured at the point of instantiation. – T.C. Jul 10 '15 at 05:53
  • 2
    For dynamic_cast to work properly, you need polymorphism, i.e. at least one virtual function. Then, since you don't even check the result, prefer using the reference form, where C++ throws an exception on failure. – Ulrich Eckhardt Jul 10 '15 at 06:04
  • This sounds like a classic example of [two-phase lookup](http://stackoverflow.com/questions/7767626/two-phase-lookup-explanation-needed). – ComicSansMS Jul 10 '15 at 13:06
  • The more idiomatic method of doing what you want to do is the CRTP . https://en.m.wikipedia.org/wiki/Curiously_recurring_template_pattern – Mark Lakata Jul 10 '15 at 15:17
  • @MarkLakata, I'm familiar with CRTP. I can't use it in this case because the base class is actually a visitor base and its type needs to be forward referenced. – ThomasMcLeod Jul 10 '15 at 15:45

1 Answers1

2

EDIT: After a little thinking: Templates firstly get definied if you instantiate them. So your first code works find cause the template firstly gets defined if the compiler has reached the line where you instantiate the template class.