Palace Chan
Palace Chan

Reputation: 9223

How to call function in derived if exists else use a default if using CRTP?

I have a helper base class for certain structs that uses CRTP to help me get some information about them:

template <class T>
struct MyHelper
{
    size_t size() const { return sizeof(T); }
};

I already have structs using this defined in various libraries in my code base, and I would like to have a way to log them. My first idea was something like this:

template <class T>
struct MyHelper
{
    size_t size() const { return sizeof(T); }
    virtual void print(std::ostream& out) const { out << "(" << sizeof(T) << ")"; }
};

but then I realized that wouldn't work for a couple reasons - one of them being adding virtual actually changes the size beyond just the POD data.

Is there a way, without the virtual keyword, for me to have a default toStr method which uses the above implementation unless the particular derived class T has an implementation in which case it defers to that?

Upvotes: 0

Views: 390

Answers (1)

Wintermute
Wintermute

Reputation: 44073

I think the most straightforward way to do something like this is to dispatch to a different non-virtual function that the derived class can shadow if it so desires. For example:

template <class T>
struct MyHelper
{
  size_t size() const { return sizeof(T); }

  void print(std::ostream &out) const {
    // Cast to derived, call do_print. This will be derived's do_print
    // if it exists and MyHelper's otherwise.
    static_cast<T const*>(this)->do_print(out);
  }

  void do_print(std::ostream& out) const {
    out << "(" << sizeof(T) << ")";
  }
};


struct A : MyHelper<A> {
  // no do_print, so MyHelper<A>'s will be used
  int x;
};

struct B : MyHelper<B> {
  // B's do_print is found and used in MyHelper<B>::print
  void do_print(std::ostream &out) const {
    out << "Hello!\n";
  }
};

template<typename T>
void foo(MyHelper<T> const &r) {
  r.print(std::cout);
}

int main() {
  A a;
  B b;

  foo(a);
  foo(b);
}

Output:

(4)Hello!

You could probably also build something with SFINAE that doesn't need a second member function, but I doubt the added complexity would be worth it.

Upvotes: 5

Related Questions