Static Polymorphism Definition and Implementation

Static polymorphism definition and implementation

  1. Static polymorphic behavior is type polymorphism that occurs at compile time rather than run time.
  2. Yes.
  3. UML is about how classes interact at runtime -- I don't believe there's a UML format for describing templates, but I could be wrong.
  4. As far as I am aware it is C++ specific, but I'm not positive given that I've not used every language ever invented. :) That said, JIT'd languages like C# and Java often are very good at removing the performance impact of indirect calls in some cases using information gleaned at runtime rather than at compile time. Whether this is at compile time or not is kind of up on the air though... after all, it is called a Just-In-Time Compiler.
  5. The main benefit is simply performance. Runtime polymorphism can do everything static polymorphism can do (in fact it can do more), but it carries the cost of indirect calls (which can be expensive if there are enough of 'em)

Now, templates themselves have many uses beyond achieving compile time polymorphism -- for example the SFINAE magic that makes boost::bind work is certainly not polymorphic -- it's merely there in order to smooth over inconsistencies in the language itself.

What is the difference between dynamic and static polymorphism in Java?

Polymorphism

1. Static binding/Compile-Time binding/Early binding/Method overloading.(in same class)

2. Dynamic binding/Run-Time binding/Late binding/Method overriding.(in different classes)

overloading example:

class Calculation {  
void sum(int a,int b){System.out.println(a+b);}
void sum(int a,int b,int c){System.out.println(a+b+c);}

public static void main(String args[]) {
Calculation obj=new Calculation();
obj.sum(10,10,10); // 30
obj.sum(20,20); //40
}
}

overriding example:

class Animal {    
public void move(){
System.out.println("Animals can move");
}
}

class Dog extends Animal {

public void move() {
System.out.println("Dogs can walk and run");
}
}

public class TestDog {

public static void main(String args[]) {
Animal a = new Animal(); // Animal reference and object
Animal b = new Dog(); // Animal reference but Dog object

a.move();//output: Animals can move

b.move();//output:Dogs can walk and run
}
}

Does static polymorphism make sense for implementing an interface?

Checking Interface.

Dynamic polymorphism does force the child to respect the interface.

Static polymorphism does NOT force the child to respect the interface
(until you really call the function), So, if you don't provide useful method,
you may use directly Impl.

class InvalidImpl {}; // Doesn't respect interface.
void bar()
{
InvalidImpl invalid;

// this compiles, as not "expected" since InvalidImpl doesn't respect Interface.
CRTP_Interface<InvalidImpl> crtp_invalid;

#if 0 // Any lines of following compile as expected.
invalid.Foo();
crtp_invalid.Foo();
#endif
}

You have a 3rd way using traits to check that a class verify an Interface:

#include <cstdint>
#include <type_traits>

// Helper macro to create traits class to know if class has a member method
#define HAS_MEM_FUNC(name, Prototype, func) \
template<typename U> \
struct name { \
typedef std::uint8_t yes; \
typedef std::uint16_t no; \
template <typename T, T> struct type_check; \
template <typename T = U> \
static yes &chk(type_check<Prototype, &T::func> *); \
template <typename > static no &chk(...); \
static constexpr bool value = sizeof(chk<U>(0)) == sizeof(yes); \
}

// Create traits has_Foo.
HAS_MEM_FUNC(has_Foo, void (T::*)(), Foo);

// Aggregate all requirements for Interface
template <typename T>
struct check_Interface :
std::integral_constant<bool, has_Foo<T>::value /* && has_otherMethod<T>::value */>
{};

// Helper macros to assert if class does respect interface or not.
#define CHECK_INTERFACE(T) static_assert(check_Interface<T>::value, #T " doesn't respect the interface")
#define CHECK_NOT_INTERFACE(T) static_assert(!check_Interface<T>::value, #T " does respect the interface")

With C++20 concepts, traits can be written differently:

// Aggregate all requirements for Interface
template <typename T>
concept InterfaceConcept = requires(T t)
{
t.foo();
// ...
};

#define CHECK_INTERFACE(T) static_assert(InterfaceConcept<T>, #T " doesn't respect the interface")

Lets test it:

class Interface {
public:
virtual void Foo() = 0;
};

class Child_Impl final : public Interface {
public:
void Foo() override {};
};

#if 0 // Following doesn't compile as expected.
class Child_InvalidImpl final : public Interface {};
#endif

template <class I>
class CRTP_Interface : public I
{
public:
void Foo() { I::Foo(); } // not actually needed
};

class Impl { public: void Foo(); }; // Do respect interface.
class InvalidImpl {}; // Doesn't respect interface.

CHECK_INTERFACE(Interface);
CHECK_INTERFACE(Child_Impl);
CHECK_INTERFACE(Impl);
CHECK_INTERFACE(CRTP_Interface<Impl>);

CHECK_NOT_INTERFACE(InvalidImpl);
CHECK_INTERFACE(CRTP_Interface<InvalidImpl>); // CRTP_Interface<T> _HAS_ Foo (which cannot be invoked)

Performance

With Dynamic Polymorphism, you may pay for virtual call. You may reduce some virtual call by adding final as class Child final : public Interface.

So compiler may optimize code like:

void bar(Child& child) { child.Foo(); } // may call Child::Foo not virtually.

but it can't do any magic (assuming bar not inlined) with:

void bar(Interface& child) { child.Foo(); } // have to virtual call Foo.

Now, assume that in your interface you have:

void Interface::Bar() { /* some code */ Foo(); }

we are in the second case where we have to virtual call Foo.

Static polymorphism solves that by:

template<class Derived>
void Interface<Derived>::Bar() { /* some code */ static_cast<Derived*>(this)->Foo(); }

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

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.

Static polymorphism: How to define the interface?

First, I think there is no problem with your static polymorphism example. Since it's static, i.e. compile-time resolved, by definition it has less strict demands regarding its interface definition.

Also it's absolutely legitimate that the incorrect code just won't compile/link, though a more clear error message from the compiler would be nicer.

If you, however, insist on interface definition, you may rewrite your example the following way:

template <class Type>
class Processor
{
public:
void doSomething(int);
void doSomethingElse();
};

template <class Type>
void op(Processor<Type>* proc){
proc->doSomething(5);
proc->doSomethingElse();
}

// specialization
template <>
class Processor<Type_Y>
{
// implement the specialized methods
};

typedef Processor<Type_Y> ProcessorY;

int main() {
ProcessorY py;
op(&py);
return 0;
}

Static polymorphism with virtual functions

You are more likely to get something the other way around, runtime polymorphism that looks like static polymorphism, or that looks like something completely different.

The metaclass proposals floated for post-reflection C++ (maybe c++26) look powerful enough to do stuff like:

Interface IBob {
void foo();
};

and

Implementation<Dispatch::Static> BobImpl:IBob {
void foo() {}
};
Implementation<Dispatch::Dynamic> BobImpl:IBob {
void foo() {}
};

to do roughly what you are asking. (Syntax is ridiculously far from final in the metaclass proposal(s); the expressive power is clearly there to do the above, however).

The dynamic case would set up vtables and the like (possibly not the standard C++ vtables however), and in the static case BobImpl would be unrelated to the type bob.

Of course, at that point, I expect there to be so many new ways to express polymorphism in C++ that "I want my CRTP to be written like a virtual function table C++ object" to be a bit like seeing atomic power technology coming over the horizon, and being excited that it could replace the coal burner on your steam-train.

Depicting static polymorphism in a UML class diagram

Combining from @ChiragDesai and @user2004268 answers and linked question (Static polymorphism definition and implementation):

  1. Polymorphism type is an implementation detail and as such it doesn't have active role in design diagrams.
  2. Implementation details can be present in UML diagram but have a complementary and informal role. Stereotypes and notes can be used to clarify the intentions.


Related Topics



Leave a reply



Submit