Replacing impure virtual functions with CRTP

I write plugins for an application through its C ++ SDK. The mechanism is quite simple. The plugin provides its functions through predefined interfaces. This is achieved due to the fact that server classes are inherited from one implementation class per interface, which contains either pure life functions or impure functions with a default implementation.
This is very practical, as SDK clients should only redefine the methods that the plugin requires and / or provide an implementation for (rare) ones without default.

What bothers me is that everything is known at compile time. Virtual function tables and mechanisms associated with run-time polymorphism are provided here only to provide default implementations.
I am trying to remove this overhead while maintaining convenience.

As an example (very contrived), let's say I have a couple of servers representing a single interface (called Blah), consisting of only one method without a default implementation.

// SDK header struct OldImpl_Blah { virtual ~OldImpl_Blah() =default; virtual int mult(int) =0; }; // plugin source class OldServer3 : public OldImpl_Blah { public: int mult(int i) override { return 3 * i; } }; class OldServer5 : public OldImpl_Blah { public: int mult(int i) override { return 5 * i; } }; 

For pure virtual functions, straightforward CRTP works just fine.

 // SDK header template <typename T> struct NewImpl_Blah { int mult(int i) { return static_cast<T*>(this)->mult(i); } }; // plugin source class NewServer3 : public NewImpl_Blah<NewServer3> { public: int mult(int i) { return 3 * i; } }; class NewServer5 : public NewImpl_Blah<NewServer5> { public: int mult(int i) { return 5 * i; } }; 

The problem is unclean virtual functions, i.e. when the default implementation is used for the method.

 // SDK header struct OldImpl_Blah { virtual ~OldImpl_Blah() =default; virtual int mult(int i) { return i; } // default }; // plugin source class OldServer3 : public OldImpl_Blah { public: int mult(int i) override { return 3 * i; } }; class OldServer5 : public OldImpl_Blah { public: int mult(int i) override { return 5 * i; } }; 

I tried combining CRTP with some SFINAE expression trick and could not.
I assume that I need some code dispatching where the base class will either provide a default implementation, or forward its implementation arguments to the derived class if one exists.
The problem is that the submission must be based on information that is not yet available to the compiler in the base class.

A simple solution would be to simply remove the virtual and override keywords in the code. But then the compiler will not check the conformity of function signatures.
Is there any well-known scheme for this situation? Is that what I ask for at all?

(Please use a few words, as my experience with templates is a bit on the side of the world. Thank you.)

+5
source share
4 answers

As always, another level of appeal is the solution. In this particular case, it is a well-known method of public non-virtual functions that call private or protected virtual functions. It has its own use, regardless of what is discussed here, so check this out independently. It usually works as follows:

 struct OldImpl_Blah { piblic: virtual ~OldImpl_Blah() = default; int mult(int i) { return mult_impl(i); } protected: virtual int mult_impl(int i) { return i; } }; // plugin source class OldServer3 : public OldImpl_Blah { protected: int mult_impl(int i) override { return 3 * i; } }; 

With CRTP, this is exactly the same:

 template <class T> struct OldImpl_Blah { piblic: virtual ~OldImpl_Blah() = default; int mult(int i) { return static_cast<T*>(this)->mult_impl(i); } protected: virtual int mult_impl(int i) { return i; } }; // plugin source class OldServer3 : public OldImpl_Blah<OldServer3> { protected: int mult_impl(int i) override { return 3 * i; } }; 

Denial of responsibility. CRTP is said to eliminate the virtual call overhead by means of nit, requiring the functions to be virtual . I don’t know if CRTP has any advantages when saving virtual functions.

+3
source

Honestly, I'm not sure I will use the following code, but I think it does what the OP asks for.
This is a minimal working example:

 #include<iostream> #include<utility> template<class D> struct B { template <typename T> struct hasFoo { template<typename C> static std::true_type check(decltype(&C::foo)); template<typename> static std::false_type check(...); static const bool value = decltype(check<T>(0))::value; }; int foo() { return B::foo<D>(0, this); } private: template<class T> static auto foo(int, B* p) -> typename std::enable_if<hasFoo<T>::value, int>::type { std::cout << "D::foo" << std::endl; return static_cast<T*>(p)->foo(); } template<class T> static auto foo(char, B*) -> typename std::enable_if<not hasFoo<T>::value, int>::type { std::cout << "B::foo" << std::endl; return 42; } }; struct A: B<A> { }; struct C: B<C> { int foo() { std::cout << "C::foo" << std::endl; return 0; } }; int main() { A a; a.foo(); std::cout << "---" << std::endl; B<A> *ba = new A; ba->foo(); std::cout << "---" << std::endl; C c; c.foo(); std::cout << "---" << std::endl; B<C> *bc = new C; bc->foo(); } 

If I did it right, there are no virtual methods, but the correct implementation of foo is called, regardless of whether you use a base class or a derived one.
Of course, it is designed around the CRTP idiom.

I know the class of member detectors is far from good.
In any case, this is enough for the purpose of the question, therefore ...

+3
source

Think about how to use policy design:

 struct DefaultMult { int mult(int i) { return i; } }; // SDK header template <typename MultPolicy = DefaultMult> struct NewImpl_Blah { int mult(int i) { return multPolicy.mult(i); } private: MultPolicy multPolicy; }; // plugin source class NewServer3 { public: int mult(int i) { return 3 * i; } }; class NewServer5 { public: int mult(int i) { return 5 * i; } }; void client() { NewImpl_Blah<NewServer5> myServer; } 

Also note that theoretically using the final keyword with override allows compilers to send more optimally than the vtable approach. I expect modern compilers to be optimized if you use the final keyword in your first implementation.

Some useful links:

  • mixin design
  • For more information on policy development, you can watch the video or read the book / article by Andrei Alexandrescu.
+2
source

I believe, I understand what you are trying to do. If I am right in my understanding, this is impossible to do.

Logically, you would like to have mult in Base to check for mult in the child structure - and if so, call it, if it is not, specify some default implementation. The downside here is that there is always mult in the child class - because it inherits the implementation of the mult test from Base. Inevitably.

The solution is to define the function in the child class differently, and in the basic test for the presence of another named function, and call it. This is a simple thing, let me know if you want to give an example. But, of course, you will lose the beauty of revaluation here.

0
source

Source: https://habr.com/ru/post/1245938/


All Articles