dimanche 14 août 2022

Use run time context varibale in compile time expression, so that CRTP pattern possible

I have a CRTP implementation for calling the corresponding implementations via base interface. I use the child classes instances as member of the SomeLogicClass class, so that I can call corresponding doSomething_Impl via interface call from the Base class.

The problem is that the child class selection (to which the doSomething_Impl() must be called), should be taken place by deciding upon a run time value mClassId of the SomeLogicClass.

The only two possible solutions I can think of are a normal switch case and SFINAE the member functions, as described in the following minimal example. (live code : https://gcc.godbolt.org/z/d8jG9bs9a)

#include <iostream>
#include <type_traits>
#define ENBALE true // to enable disable test solutions

enum struct Type : unsigned { base = 0, child1, child2, child3 /* so on*/ };

// CRTP Base
template<typename Child> struct Base {
    void doSomething() { static_cast<Child*>(this)->doSomething_Impl(); }
private:
    Base() = default;
    friend Child;
};

struct Child1 : public Base<Child1> {
    void doSomething_Impl() { std::cout << "Child1 implementation\n"; }
};
struct Child2 : public Base<Child2> {
    void doSomething_Impl() { std::cout << "Child2 implementation\n"; }
}; 
struct Child3 : public Base<Child3> {
    void doSomething_Impl() { std::cout << "Child3 implementation\n"; }
};
// ... so on

class SomeLogicClass
{
    Type mClassId{ Type::base };
    Child1 mChild1;
    Child2 mChild2;
    Child3 mChild3;
    // ... child3  so on!

public:
    Type getId() const { return mClassId; }
    void setId(Type id) { mClassId = id; } // run time depended!

#if ENBALE // Solution 1 : simple case
    /*what in C++11?*/ getInstance()
    {
        switch (mClassId)
        {
        case Type::child1: return mChild1;
        case Type::child2: return mChild2;
        case Type::child3: return mChild3;
        default:  // error case!
            break;
        }
    }
#elif !ENBALE // Solution 2 : SFINAE

    template<Type ID>
    auto getInstance() -> typename std::enable_if<ID == Type::child1, Child1&>::type { return mChild1; }
    template<Type ID>
    auto getInstance() -> typename std::enable_if<ID == Type::child2, Child2&>::type { return mChild2; }
    template<Type ID>
    auto getInstance() -> typename std::enable_if<ID == Type::child3, Child3&>::type { return mChild3; }
#endif
   
};

void test(SomeLogicClass& ob, Type id)
{
    ob.setId(id);
#if ENBALE // Solution 1
    auto& childInstance = ob.getInstance();
#elif !ENBALE // Solution 2
    auto& childInstance = ob.getInstance<ob.getId()>();
#endif
    childInstance.doSomething(); // calls the corresponding implementations!
}

int main() 
{
    SomeLogicClass ob;    
    test(ob, Type::child1);
    test(ob, Type::child2);
    test(ob, Type::child3);
}

As noted in the comments in the above code, both can't get work, for the reasons

  • Solution 1: the member function must have a unique return type
  • Solution 2: SFINAE required compile time expression to decide which overload to be chosen.

Dynamic polymorphism would be the easiest solution here (Solution 1 works with Base&). However, I would like to know if we got some way around still keeping the static polymorphism under the compiler flag ?

I checked this already given answer: Optimize template replacement of a switch however, I could not figure the actual application in my case.

Even though I am looking for something compilable under C++11, it might be interesting for future readers to know something working in the latest standards as well.

Aucun commentaire:

Enregistrer un commentaire