c++crtpdowncaststatic-castprivate-inheritance

In C++, is it possible to use CRTP with a private base?


In C++ I have many classes, unrelated by inheritance, that define a method std::string get_name() const.

There are several utility functions that many classes need that are implemented in terms of get_name(). I would like classes that implement get_name() to also get these utility functions. To use a silly example let's say that for every class that has get_name, I want to have void print_name() const { std::cout << get_name() << std::endl; }. There are a few such functions, though, and I don't want to repeat them in each class that needs them.

Sounds like a job for CRTP:

template <class T>
class NameMethods {
public:

  void print_name() const {
     std::cout << static_cast<const T&>(*this).get_name() << std::endl;
  }

};

Works great.

Here's the wrinkle - what if I want print_name() to be private in the subclass? If the subclass inherits privately from NameMethods, then the downcast won't compile (inaccessible base). I could replace it with a reinterpret_cast, a trivial example would work, but a reinterpret_cast won't do the right thing if there's any multiple inheritance, which there might be in the general case.

Is there an easy way to do this sort of thing?

Thanks!

Edit: I should add a little background. I'm trying to write in a style that uses protocols where possible, rather than abstract superclasses. If I make an abstract superclass with a public get_name() and a protected print_name(), I could easily get everything to work. The problem is that I don't want an abstract superclass. It would have several subclasses that I would like to be unrelated (because I eventually want to compose them using mixins or MI).

My idea is that I will compose a concrete class, and somewhere in that class or in a superclass, something will implement get_name(). That concrete class will want a set of utility functions, but those utility functions shouldn't be part of the interface.


Solution

  • Yes, you can. Just make your CRTP base class friend of your class, and the downcast will work again:

    template <class T>
    class NameMethods {
    public:
    
      void print_name() const {
         std::cout << static_cast<const T&>(*this).get_name() << std::endl;
      }
    
    };
    
    class MyClass: private NameMethods<MyClass>
    {
    public:
        std::string get_name() const { return "MyClass";}
        void get_info() const
        {
            print_name();
        }
    private:
        friend class NameMethods<MyClass>;
    };
    
    int main()
    {
        MyClass c;
        c.get_info();
    
        return 0;
    }
    

    See live example: https://godbolt.org/z/5oQvSq