Search code examples
c++crtpstatic-polymorphism

Can I use template member functions as an alternative to the CRTP in order to implement static polymorphism?


I want to implement the template method pattern using static polymorphism in C++.

Right now I have two classes A and B which are very similar. They have an identical public API and the exact same list of private data members. Most of the public member functions are also implemented identically but for some of them the two classes differ slightly in their implementation.

The situation looks somewhat like this:

class A {
public:
  /* Ctor etc... */
  int One() const;
  int Two() const;
  int Three() const; /* Calls PrivateA() internally */
private:
  int PrivateA() const;
  int a;
  int b;
  int c;
};

class B {
public:
  /* Ctor etc... */
  int One() const; /* Identical to A::One() */
  int Two() const; /* Identical to A::Two() */
  int Three() const; /* Same as in A::Three() but calls PrivateB() instead of PrivateA() internally */
private:
  int PrivateB() const; /* Specific for B */
  /* Identical data members (type and semantics) to A */
  int a;
  int b;
  int c;
};

I now want to reduce code duplication by moving all the shared code and data members to a base class and delegate to sub-class specific member functions from within the base class'. That is, something like this:

class Base {
public:
  int One() const;
  int Two() const;
  int Three() const; /* Should delegate to Private() internally */
private:
  int a;
  int b;
  int c;
};

class A : public Base {
private:
  int Private() const;
};

class B : public Base {
private:
  int Private() const;
};

I know I can solve this using the CRTP in a manner similar to this:

template<typename T>
class Base {
public:
  int One() const;
  int Two() const;
  int Three() const; /* Should delegate to Private() internally */
private:
  int a;
  int b;
  int c;
};

class A : public Base<A> {
private:
  friend class Base<A>;
  int Private() const;
  int p;
};

class B : public Base<B> {
private:
  friend class Base<B>
  int Private() const;
  int q;
};

template<typename T>
int Base<T>::Three() const {
  return static_cast<const T*>(this)->Private();
}

int A::Private() const { return this->p; }
int B::Private() const { return this->q; }

int main() {
  A a{};
  B b{};

  /* Uses shared code from base class */
  a.One();
  b.One();

  /* Base class code statically dispatches to sub-class code via CRTP */
  a.Three(); /* Returns value of p */
  b.Three(); /* Returns value of q */

  return 0;
}

Now onto my question.

Is it possible to achieve the same result without making Base a template class? Can I accomplish the same thing using e.g. template member functions?

/* Note: no template */
class Base {
public:
  int One() const;
  int Two() const;

  template<typename T>
  int Three() const; /* Should delegate to Private() internally */
private:
  int a;
  int b;
  int c;
};

Solution

  • If the question is strictly "can I" - the answer is than "yes, you can, quite trivially":

    template<typename T>
    int Three() const {
        return static_cast<const T*>(this)->Private();
    }
        
    

    However, I would not advise this, since one of the benefits of CRTP is that it is (almost) impossible to misuse the cast - static_cast will almost always be valid.

    With a snippet shown above, one could easily make a mistake and provide wrong template argument, and make static_cast undefined behavior.

    Conclusion - there is no benefit in using this approach, but one can find a drawaback.