Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does static polymorphism make sense for implementing an interface?

and Merry Christmas everybody!

I am learning about static polymorphism and I'm reading Andrei Alexandrescu's excellent book on policy-based design. I came across the following, in my code: I have interface Interface which specifies that method Foo must be present. This interface will be implemented by class Impl. I have the following two options:

1) Dynamic polymorphism

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

class Impl : public Interface {
public:
    void Foo() {};
}

2) Static polymorphism

class Impl {
{
public:
    void Foo() {};
}

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

Does it make sense to use static polymorphism in this case? Does the second approach offer any benefits compared to the first one? The interface specifies only the presence of some methods, and its mechanics are the same for different implementation - so not quite like the cases described in the book, so I feel I may only be over-complicating things.

Update: I do not need polymorphic behaviour at run-time; the correct implementation is known at compile-time.

like image 762
Dan Nestor Avatar asked Dec 25 '13 09:12

Dan Nestor


People also ask

Can polymorphism be implemented using interfaces?

Interfaces formalize polymorphism. Interfaces allow us to define polymorphism in a declarative way, unrelated to implementation. Two elements are polymorphic with respect to a set of behaviors if they realize the same interfaces.

Can static methods implement interface?

Similar to Default Method in Interface, the static method in an interface can be defined in the interface, but cannot be overridden in Implementation Classes. To use a static method, Interface name should be instantiated with it, as it is a part of the Interface only.

How polymorphism is achieved with the help of interface?

Polymorphsim via interfaces Each interface is considered as a type. An object of a class can be casted to the type of each interface it implements. This is how polymorphism via interfaces work.

Which is true about static polymorphism?

9. Which of the following is correct about static polymorphism? Explanation: The conflict between which function to call is resolved during the compile time in static polymorphism i.e. before the execution of the program starts.


1 Answers

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(); }
like image 176
Jarod42 Avatar answered Sep 22 '22 03:09

Jarod42