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?