Demaunt
Demaunt

Reputation: 1243

Polymorphism in template functions

I want to use template function for handling both polymorphic and non-polymorphic classes. Here are 3 basic classes.

class NotDerived
{

};

class Base
{
public:
    virtual ~Base() {}
    void base_method() {}
};

class Derived : public Base
{

};

Since NotDerived has no virtual functions, I can't use dynamic_cast, next comes template function:

template<class T>
auto foo(T& some_instance)
{
    if (std::is_base_of_v<Base, T>)
    {
        //CASE_1: Works for d and b
        /*some_instance.base_method();*/

        //CASE_2: Works for d and b
        /*auto lamb1 = [](T& some_instance) {some_instance.base_method(); };
        lamb1(some_instance);*/


        auto lamb2 = [](T& some_instance) {((Base&)some_instance).base_method(); };
        lamb2(some_instance);
    }
}

The main functions does this:

void main()
{
    Derived d{};
    Base b{};
    NotDerived nd{};

    foo(d);
    foo(b);
    foo(nd);
}

Now I understand why CASE_1 does not work in case of passing nd variable, but what I can't understand is that I have to explicitly cast some_instance in lamb2 function in order to call base_method.

Can someone explain why CASE_1, CASE_2 do not work, while CASE_3 works. By working I mean calling base_method if possible without dynamic_casting.

Also is it possible to use constexpr for handling such cases of static polymorphism or compiled polymorphism(Hope it is legal to name it like this)

Upvotes: 1

Views: 237

Answers (2)

mohabouje
mohabouje

Reputation: 4050

You should take a look into the official doc about SFINAE. The code inside your if will get compiled in any case, so that's why it wont compile.

If you are using C++17, you can replace it with a constexpr if, that will evaluate the information inside the if during compilation time and ignore the code if needed:

template<class T>
auto foo(T& some_instance) {
    if constepxr (std::is_base_of_v<Base, T>) {
        auto lamb2 = [](T& some_instance) {((Base&)some_instance).base_method(); }
        lamb2(some_instance);
    } else {
        // Whatever you want to do
    }
}

If you are using an older version of C++ a simple function overload may solve your problem:

template<class T>
auto foo(T& some_instance) {
   // Do whatever
}

auto foo(Base& some_instance) {
    auto lamb2 = [](T& some_instance) {((Base&)some_instance).base_method(); }
    lamb2(some_instance);
}

As an alternative, you can use SFINAE mechanism to peek the right function by using std::enable_if:

template <typename T>
typename std::enable_if<std::is_base_of<Base, T>::value, void>::type
foo() {
   auto lamb2 = [](T& some_instance) {((Base&)some_instance).base_method(); }
   lamb2(some_instance); 
}

template <typename T>
typename std::enable_if<!std::is_base_of<Base, T>::value, void>::type
foo() {
   // Do whatever
}

Upvotes: 1

NathanOliver
NathanOliver

Reputation: 180415

Case 3 does not work. You are casting to an unrelated type and using that temporary to call the function which is undefined behavior.

The problem with using

if (std::is_base_of_v<Base, T>)
{
    //...
}

is that everything in the ... part needs to be able to compile. What you need is a way to only call that code when T is the type you want. You can use constexpr if like

if constexpr (std::is_base_of_v<Base, T>)
{
    some_instance.base_method();
}
else
{
    // do something else since T isn't a Base or derived from Base
}

And now if std::is_base_of_v<Base, T> is false then some_instance.base_method(); will be discarded and never compiled.

Upvotes: 5

Related Questions