What Is the Motivation Behind Static Polymorphism in C++

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.

Dyamic vs Static Polymorphism in C++ : which is preferable?

A switch is nothing more than a sequence of jumps that -after optimized- becomes a jump to an address looked-up by a table. Exactly like a virtual function call is.

If you have to jump depending on a type, you must first select the type. If the selection cannot be done at compile time (essentially because it depends on the input) you must always perform two operation: select & jump. The syntactic tool you use to select doesn't change the performance, since optimize the same.

In fact you are reinventing the v-table.

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(); }

how to make static polymorphic member variables

you can do something like this if you really need to, but I suggest just making a constructor in base that accepts an id and pass it from the childs to the parent

int getNextId()
{
static int lastId{0};
return lastId++;
}

template<typename T>
int getDerivedId()
{
static int id{getNextId()};
return id;
}

struct Base {
};

struct Derived0 final : Base {
};

struct Derived1 final : Base {
};

int main() {
return !(getDerivedId<Derived0>() == 0 && getDerivedId<Derived1>() == 1);
}

what is the equivalent of pure abstract function in static polymorphism?

Question:

What is the equivalent with static polymorphism?

Declare a function template without an implementation. Create implementations only for the types that you want to support.

// Only the declaration.
template<typename T> string f();

// Implement for float.
template<> string f<float>() { return "float"; }

f<int>(); // Error.
f<float>(); // OK

Update

Use of static_assert:

#include <string>

using std::string;

template<typename T> string f() { static_assert((sizeof(T) == 0), "Not implemented"); return "";}

// Implement for float.
template<> string f<float>() { return "float"; }

int main()
{
f<int>(); // Error.
f<float>(); // OK
return 0;
}

Compiler report:

g++ -std=c++11 -Wall    socc.cc   -o socc
socc.cc: In function ‘std::string f()’:
socc.cc:6:35: error: static assertion failed: Not implemented
<builtin>: recipe for target `socc' failed

How to defined a static interface in base class and make sure the interface must be implement in derived class?

It is not possible to make a virtual static function. For the simple reason that when calling a static function, you always know the class that defines that function in compile time. Unlike virtual functions, where you don't know the type of the object whose method you're calling.
For example:

class A
{
public:
virtual void f() {printf("A");}
};

class B : public A
{
virtual void f() override {printf("B");}
};

void g(A& a)
{
a.f();
}

int main()
{
B b;
g(b);
return 0;
}

In the above example, inside the function g, the correct function is invoked (B::f). Even though while compiling the function it is not known what the type of its argument is (it could be A or any class derived from A).
Without making f() virtual, you would have overloaded the method f, rather than overridden it. Which means that in the following example, the output would be "A", even though you might expect it to be "B":

class A
{
public:
void f() {printf("A");}
};

class B : public A
{
void f() {printf("B");}
};

void g(A& a)
{
a.f();
}

int main()
{
B b;
g(b);
return 0;
}

This may cause serious bugs, and it is suggested to never overload base class methods, and to always use the override keyword when overriding a virtual method to escape those bugs.

When making a static function, you can simply overload it, it would not create a compilation error. However, you probably never should overload it, because it may hide a bug that is very difficult to track (you are certain that B::f() is being called while actually A::f() is being called).
Furthermore, it is not possible to 'force' the derived class to implement a static interface, because there is no such thing as a static interface. Because you have no virtual static functions, you may not pass a reference or pointer to the interface that would implement this function.



Related Topics



Leave a reply



Submit