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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With