1

Wikipedia has a nice example that explains how the CRTP-Pattern can be used to implement polymorphic chaining of member function calls.

The example features a base class template "Printer", and a derived class "CoutPrinter". I have a similar use-case, but I need an additional "middle" layer. The following code works nicely:

#include <iostream>

using namespace std;

template <typename T>
class Top
{
public:
    Top(ostream &pstream) : m_stream(pstream) {}

    T& top() { m_stream << "top\n"; return *static_cast<T*>(this); }

protected:
    ostream& m_stream;
};

template <typename T>
class Middle : public Top<T>
{
public:
    Middle(ostream &pstream) : Top<T>(pstream) {}

    // why not just m_stream << "middle\n"; ?
    T& middle() { static_cast<T*>(this)->m_stream << "middle\n"; return *static_cast<T*>(this); }
};

struct Bottom : public Middle<Bottom>
{
public:
    Bottom(ostream &pstream) : Middle<Bottom>(pstream) {}

    Bottom& bottom() { m_stream << "bottom\n"; return *this; }
};

int main()
{
    Bottom(cout).bottom().middle().top().bottom().middle().top().middle().bottom();

    return 0;
}

As expected this prints:

bottom
middle
top
bottom
middle
top
middle
bottom

The implementation of top() is:

    T& top() { m_stream << "top\n"; return *static_cast<T*>(this); }

and the implementation of bottom() is:

    Bottom& bottom() { m_stream << "bottom\n"; return *this; }

gcc and clang won't let me write:

    T& middle() { m_stream << "middle\n"; return *static_cast<T*>(this); }

because they are of the opinion that m_stream is undeclared in the scope of middle(). However, both accept:

    T& middle() { static_cast<T*>(this)->m_stream << "middle\n"; return *static_cast<T*>(this); }

I do not fully understand why m_stream cannot be accessed directly, and an extra cast of this to T* is needed.

I can imagine that from the perspective of Middle it is impossible to figure out whether every (hypothetical) specialization of Top for some T contains an m_stream member, but Bottom is a concrete class and can therefore look at its own interface.

I would like to understand the underlying rule.

To summarize: Why can we directly access m_stream from Top and Bottom, but not from Middle?

Samuel Liew
  • 76,741
  • 107
  • 159
  • 260
pek
  • 33
  • 3
  • 1
    Does this answer your question? [templates: parent class member variables not visible in inherited class](https://stackoverflow.com/questions/6592512/templates-parent-class-member-variables-not-visible-in-inherited-class) – walnut Jan 13 '20 at 14:55
  • 1
    Note that the `static_cast` is not needed. Just using `this->m_stream` is sufficient. – walnut Jan 13 '20 at 14:56

1 Answers1

2
T& middle() { this->m_stream << "middle\n"; return *static_cast<T*>(this); }

that will also work.

Template methods do not look into dependent base classes, because at the time when you are compiling the template (and not instantiating it) the compiler cannot tell if you are referring to a base class member you'll get from T or a global variable with the same name.

Rather than make it switch between accessing a global variable or not depending on what T contains, the C++ standard makes never look into T (and other template-argument dependent base classes) when finding names.

To look in the class, use this-> or similar.

That way, you won't have a case where some T arguments lead to m_stream being a member variable, and others lead to it being a global variable. Because that leads to seriously insane bugs.

Now, Bottom doesn't follow these rules, because Middle<Bottom> is not dependent on any of the (non-existent) template arguments to Bottom. What matters is what base classes whose type is dependent on the template arguments to yourself.

So in Bottom, the fact it inherits from Middle<Bottom> and Top<Bottom> is fully known.


As an aside, in a base CRTP class, I often have a method:

T& self() { return *static_cast<T*>(this); }
T const& self() const { return *static_cast<T const*>(this); }

as a general pattern.

Then I can write:

T& middle() { this->m_stream << "middle\n"; return this->self(); }

and stop messing around with static_cast to the CRTP most-derived elsewhere.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524