Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the motivation behind static polymorphism in C++?

I understand the mechanics of static polymorphism using the Curiously Recurring Template Pattern. I just do not understand what is it good for.

The declared motivation is:

We sacrifice some flexibility of dynamic polymorphism for speed.

But why bother with something so complicated like:

template <class Derived> class Base { public:     void interface()     {          // ...          static_cast<Derived*>(this)->implementation();          // ...     } };  class Derived : Base<Derived> { private:      void implementation(); }; 

When you can just do:

class Base { public:      void interface(); }  class Derived : public Base { public:      void interface(); } 

My best guess is that there is no semantic difference in the code and that it is just a matter of good C++ style.

Herb Sutter wrote in Exceptional C++ style: Chapter 18 that:

Prefer to make virtual functions private.

Accompanied of course with a thorough explanation why this is good style.

In the context of this guideline the first example is good, because:

The void implementation() function in the example can pretend to be virtual, since it is here to perform customization of the class. It therefore should be private.

And the second example is bad, since:

We should not meddle with the public interface to perform customization.

My question is:

  1. What am I missing about static polymorphism? Is it all about good C++ style?
  2. When should it be used? What are some guidelines?
like image 588
Martin Drozdik Avatar asked Sep 28 '13 02:09

Martin Drozdik


People also ask

Why is polymorphism static?

Static Polymorphism is the linking of a function with an object during compile time is called static. It is also called static binding. C# provides two techniques to implement static polymorphism i.e. Function overloading and Operator overloading.

What is the purpose of polymorphism in C++?

Polymorphism means "many forms", and it occurs when we have many classes that are related to each other by inheritance. Like we specified in the previous chapter; Inheritance lets us inherit attributes and methods from another class. Polymorphism uses those methods to perform different tasks.

Why do we need dynamic polymorphism?

Dynamic Polymorphism allows Java to support overriding of methods which is central for run-time polymorphism. It allows a class to specify methods that will be common to all of its derivatives while allowing subclasses to define the specific implementation of some or all of those methods.

What is the difference between static and dynamic polymorphism?

Static polymorphism is polymorphism that occurs at compile time, and dynamic polymorphism is polymorphism that occurs at runtime (during application execution). An aspect of static polymorphism is early binding. In early binding, the specific method to call is resolved at compile time.


1 Answers

What am I missing about static polymorphism? Is it all about good C++ style?

Static polymorphism and runtime polymorphism are different things and accomplish different goals. They are both technically polymorphism, in that they decide which piece of code to execute based on the type of something. Runtime polymorphism defers binding the type of something (and thus the code that runs) until runtime, while static polymorphism is completely resolved at compile time.

This results in pros and cons for each. For instance, static polymorphism can check assumptions at compile time, or select among options which would not compile otherwise. It also provides tons of information to the compiler and optimizer, which can inline knowing fully the target of calls and other information. But static polymorphism requires that implementations be available for the compiler to inspect in each translation unit, can result in binary code size bloat (templates are fancy pants copy paste), and don't allow these determinations to occur at runtime.

For instance, consider something like std::advance:

template<typename Iterator> void advance(Iterator& it, ptrdiff_t offset) {     // If it is a random access iterator:     // it += offset;     // If it is a bidirectional iterator:     // for (; offset < 0; ++offset) --it;     // for (; offset > 0; --offset) ++it;     // Otherwise:     // for (; offset > 0; --offset) ++it; } 

There's no way to get this to compile using runtime polymorphism. You have to make the decision at compile time. (Typically you would do this with tag dispatch e.g.)

template<typename Iterator> void advance_impl(Iterator& it, ptrdiff_t offset, random_access_iterator_tag) {     // Won't compile for bidirectional iterators!     it += offset; }  template<typename Iterator> void advance_impl(Iterator& it, ptrdiff_t offset, bidirectional_iterator_tag) {     // Works for random access, but slow     for (; offset < 0; ++offset) --it; // Won't compile for forward iterators     for (; offset > 0; --offset) ++it; }  template<typename Iterator> void advance_impl(Iterator& it, ptrdiff_t offset, forward_iterator_tag) {      // Doesn't allow negative indices! But works for forward iterators...      for (; offset > 0; --offset) ++it; }  template<typename Iterator> void advance(Iterator& it, ptrdiff_t offset) {     // Use overloading to select the right one!     advance_impl(it, offset, typename iterator_traits<Iterator>::iterator_category()); }   

Similarly, there are cases where you really don't know the type at compile time. Consider:

void DoAndLog(std::ostream& out, int parameter) {     out << "Logging!"; } 

Here, DoAndLog doesn't know anything about the actual ostream implementation it gets -- and it may be impossible to statically determine what type will be passed in. Sure, this can be turned into a template:

template<typename StreamT> void DoAndLog(StreamT& out, int parameter) {     out << "Logging!"; } 

But this forces DoAndLog to be implemented in a header file, which may be impractical. It also requires that all possible implementations of StreamT are visible at compile time, which may not be true -- runtime polymorphism can work (although this is not recommended) across DLL or SO boundaries.


When should it be used? What are some guidelines?

This is like someone coming to you and saying "when I'm writing a sentence, should I use compound sentences or simple sentences"? Or perhaps a painter saying "should I always use red paint or blue paint?" There is no right answer, and there is no set of rules that can be blindly followed here. You have to look at the pros and cons of each approach, and decide which best maps to your particular problem domain.


As for the CRTP, most use cases for that are to allow the base class to provide something in terms of the derived class; e.g. Boost's iterator_facade. The base class needs to have things like DerivedClass operator++() { /* Increment and return *this */ } inside -- specified in terms of derived in the member function signatures.

It can be used for polymorphic purposes, but I haven't seen too many of those.

like image 105
Billy ONeal Avatar answered Sep 29 '22 07:09

Billy ONeal