There is a base class Base
, that's had much functionality in it, and can/should be used as-is. But I want to extend it with (several) Derived
classes, without polluting the original class with a bunch of virtual/overrides (because, it's uncertain which methods will actually need to be changed from the Base version). Of course, naively having a Derived object call an inherited method makes that method only call the methods of the base class, not any overridden.
I've tried to make it CRTP, but the hangup seems to be on the base class; if Base is a template, how can there be an instantiation of itself, with itself, when the type is incomplete at that time? Most other SO questions don't seem to actually use the base class, keeping it as an interface definition. Alternatively, is the answer in instead function templates of the members that currently hardcode Base, instead of a class template?
// template <class T = struct Base>
struct Base {
Base& genericWrapper(); // return T&
std::string specificConfig();
Base& methodChaining(); // return T&
};
Base& Base::genericWrapper(){
std::cout << "Base::genericWrapper():\n" <<
/* T:: */ specificConfig() << std::endl;
return *this; // T??
}
std::string Base::specificConfig(){
return "Base::specificConfig()";
}
Base& Base::methodChaining(){
std::cout << ".Base::methodChaining()\n";
return *this;
}
struct Derived : public Base {
std::string specificConfig();
Derived& methodChaining(); // return T&
};
std::string Derived::specificConfig(){
return "Derived::specificConfig()";
}
Derived& Derived::methodChaining(){
std::cout << ".Derived::methodChaining()\n";
return *this;
}
int main()
{
Base b{};
Derived d{};
std::cout << "Base:\n";
b.genericWrapper().methodChaining().methodChaining();
std::cout << "\nDerived:\n";
d.genericWrapper().methodChaining().methodChaining();
return 0;
}
Which prints:
Derived:
Base::genericWrapper(): // OK
Base::specificConfig() // No
.Base::methodChaining() // Also no
.Base::methodChaining()
If you want to use CRTP, then you do need a base class template, which as you say you can't instantiate with itself, but you can create a derived struct
to instantiate the base class template with that doesn't alter the behaviour (below, TriviallyDerived
). When using CRTP, there's a bit of static casting required, as you'll see below.
#include <string>
#include <iostream>
template <class Derived>
struct Base {
Derived& genericWrapper();
std::string specificConfig();
Derived& methodChaining();
};
template <class Derived>
Derived& Base<Derived>::genericWrapper(){
std::cout << "Base::genericWrapper():\n" <<
static_cast<Derived*>(this)->specificConfig() << std::endl;
return static_cast<Derived&>(*this);
}
template <class Derived>
std::string Base<Derived>::specificConfig(){
return "Base::specificConfig()";
}
template <class Derived>
Derived& Base<Derived>::methodChaining(){
std::cout << ".Base::methodChaining()\n";
return static_cast<Derived&>(*this);
}
struct Derived : Base<Derived> {
std::string specificConfig();
Derived& methodChaining();
};
std::string Derived::specificConfig(){
return "Derived::specificConfig()";
}
Derived& Derived::methodChaining(){
std::cout << ".Derived::methodChaining()\n";
return *this;
}
struct TriviallyDerived : Base<TriviallyDerived> { };
int main()
{
TriviallyDerived b{};
Derived d{};
std::cout << "Base:\n";
b.genericWrapper().methodChaining().methodChaining();
std::cout << "\nDerived:\n";
d.genericWrapper().methodChaining().methodChaining();
}