Home > Enterprise >  Concept-ed vs CRTP static/compile-time polymorphism - using base class methods and variables from de
Concept-ed vs CRTP static/compile-time polymorphism - using base class methods and variables from de

Time:12-10

I have been learning about c 20 concepts as an alternative to the template design of CRTP for static polymorphism (for those who do not know what I am talking about, here is a nice resource: https://www.fluentcpp.com/2020/09/11/replacing-crtp-static-polymorphism-with-concepts/)

The nice thing about CRTP is that you can have a variable in the base class and use it in the subclass. Here is a working example where shared is the shared variable. By this same logic, we can call constructors and methods of the base class within the derived class.

template <class TImpl>
class Base {
private:
  TImpl &impl = static_cast<TImpl &>(*this);
  friend TImpl;
  int shared = 0;

public:
  void say_hello() { impl.do_say_hello(); }
};

class Derived : public Base<Derived> {
  friend Base<Derived>;
  void do_say_hello() {
    shared = 3;
    cout << "Hello " << shared << endl;
  }
};

int main() {
  Derived d;
  d.say_hello();
}

However this is where I get stuck with c 20 concepts. I can easily call derived class methods from the base class, but not vice versa, which feels a bit like reverse polymorphism. Does anyone know how I can call base class methods and use variables of the base class in the derived class with Concept-ed static polymorphism?

CodePudding user response:

The CRTP is a C tool for implementing the concept of a mixin in C . A mixin is a way of injecting some generic functionality into a specific type's interface. So this involves two separate pieces of code: the generic functionality, and the interface into which this functionality is injected.

The article effectively proposes a way of doing a mixin through free functions. The article is essentially implementing a lame form of the customization point idiom (and then presenting it like it's a discovery instead of just a concept-based version of std::begin. You know, like std::ranges::begin).

Customization points are generally one-way streets in terms of communication. The generic customization point function can inspect the interface it is augmenting, but that interface cannot itself interact with the generic functionality. At least, not easily and not in a non-public way.

By contrast, the CRTP allows easy two-way communication. The generic functionality (the template base class) can call derived class functions through a cast of this to the template parameter (and C 23 makes this way easier). And by virtue of being the derived class, the class being injected into can communicate to the base class, if the base class exposes protected interface members for that purpose.

The intermediate form proposed by the article (where the generic functionality is the derived class and the target of the injection is the base class) is also primarily one-way: generic-to-target. The CRTP cast works because the base class is given the derived class type. Template instantiation rules allow that derived class type to be used in certain ways despite that type not being complete at the point of inheritance.

So when you make the base a non-template class that is not given the derived class name, you cut off its ability to do the casting trick. Which means that inverting the hierarchy like this makes the mixin communication one-way. Though that C 23 trick does allow you to reclaim two-way communication.

  • Related