Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add extra methods at compile time according to some enum value

Imagine I have the following class:

class Extra final
{
public:

    void Method1() const {std::cout << "Method 1" << std::endl;}
    void Method2() const {std::cout << "Method 2" << std::endl;}
};

I created, for some class T, a class template that can enable/disable methods using Extra at compile time according to different scenarios (listed in an enum):

enum class Scenario
{
    None, // No extra.
    One,  // Only Method1 allowed.
    Two,  // Only Method2 allowed.
    Both  // Both Method1 and Method2 allowed.
};

The class template looks as follow:

template<typename T, Scenario R>
class ExtraAdder : public T
{
};

template<typename T>
class ExtraAdder<T, Scenario::One> : public T
{
public:

    void Method1Extra() const {m_extra.Method1();}

private:

    Extra m_extra;
};

template<typename T>
class ExtraAdder<T, Scenario::Two> : public T
{
public:

    void Method2Extra() const {m_extra.Method2();}

private:

    Extra m_extra;
};

template<typename T>
class ExtraAdder<T, Scenario::Both> : public T
{
public:

    void Method1Extra() const {m_extra.Method1();}
    void Method2Extra() const {m_extra.Method2();}

private:

    Extra m_extra;
};

It can be used as follow:

class Base
{
public:

    virtual ~Base() = default;
    void BaseMethod() const {std::cout << "Base method" << std::endl;}

    // Some other stuff...
};

int main(int argc, char **argv)
{
    std::cout << "Scenario: None" << std::endl;
    ExtraAdder<Base, Scenario::None> none;
    none.BaseMethod();
    //none.Method1Extra();   // Does not compile.
    //none.Method2Extra();   // Does not compile.

    std::cout << std::endl << "Scenario: One" << std::endl;
    ExtraAdder<Base, Scenario::One> one;
    one.BaseMethod();
    one.Method1Extra();
    //one.Method2Extra();    // Does not compile.

    std::cout << std::endl << "Scenario: Two" << std::endl;
    ExtraAdder<Base, Scenario::Two> two;
    two.BaseMethod();
    //two.Method1Extra();    // Does not compile.
    two.Method2Extra();

    std::cout << std::endl << "Scenario: Both" << std::endl;
    ExtraAdder<Base, Scenario::Both> both;
    both.BaseMethod();
    both.Method1Extra();
    both.Method2Extra();

    return 0;
}

The point is that by changing the template parameter Scenario, I can only have access to some method(s) of the wrapped m_extra member, which is exactly what I want. However, as you can see, the different specializations are very verbose (and have some duplication).

Question: Is there a way to produce the exact same template specializations, but in a less verbose way, with limited or no duplication?

The closest I have found to solve this is this question (using std::enable_if), but I have not been able to adapt it to my case.

I am using g++ with the C++17 standard.

like image 653
BobMorane Avatar asked Jan 16 '21 20:01

BobMorane


1 Answers

Alternatively, you can put the enable_if into the template instead of the return type declaration. I personally find this a bit more readable, and it has the advantage that you can use return type deduction.

template<typename T, Scenario R>
class ExtraAdder : public T {
    Extra m_extra;
public:
    template<
        Scenario S = R, 
        typename = std::enable_if_t<(S == Scenario::One || S == Scenario::Both)>
    >
    void Method1Extra() const {
        m_extra.Method1();
    }

    
    template<
        Scenario S = R, 
        typename = std::enable_if_t<(S == Scenario::Two || S == Scenario::Both)>
    >
    decltype(auto) Method2Extra() const {
       return m_extra.Method2();
    }

};

Live example here.

A common pitfall of using enable_if with member functions is that it only works if substitution of a template argument is ill-formed. Therefore we need to add that extra template parameter Scenario S = R (or the bool in the other answer) in order to defer the substitution to the point where the method is being used. Otherwise there wouldn't be such a substitution resulting in a hard error. See this question for a more in-depth discussion.

like image 146
florestan Avatar answered Sep 27 '22 23:09

florestan