What I want to achieve
The motivation is I want to have template with form
template<typename T, typename ...Args>
where T
is some class type. And I want a specialised version of this template if T
is BaseA
or any derived class of BaseA
.
(The C++ standard I'm under is C++20)
The problem
If the second template param is definite, this is easy to achieve:
template <typename T, typename U, typename Enable = void>
class Factory
{
public:
Factory(U arg)
{
m_spInner = std::make_shared<T>(arg);
}
void print() const
{
printf("Factory normal: ");
m_spInner->print();
}
private:
std::shared_ptr<T> m_spInner;
};
template <typename T, typename U>
class Factory<T, U, typename std::enable_if<std::is_base_of<BaseA, T>::value>::type>
{
public:
Factory(U arg)
{
m_spInner = std::make_shared<T>(arg);
}
void print() const
{
printf("Factory BaseA: ");
m_spInner->print();
}
private:
std::shared_ptr<T> m_spInner;
};
// callers
int main()
{
Factory<DerivedA1, int> factoryA1(1);
factoryA1.print();
Factory<DerivedA2, int> factoryA2(2);
factoryA2.print();
Factory<DerivedB, char> factoryB('B');
factoryB.print();
Factory<C, string> factoryC("C");
factoryC.print();
//Factory<int> factoryInt(1); // compile error
//factoryInt.print();
return 0;
}
does the job.
But when the second param is indefinite, like
template <typename T, typename ...Args, typename Enable = void>
it does not compile with error message that the indefinite param must be the last param.
If I put the Enable
before the ...Args
, the template itself can compile but the calling doesn't work, as int
doesn't fit the Enable
template param.
So I wonder if it is possible to make enable_if
work with class template with indefinite param. Or is there any other elegant way to achieve the need to specialise template for a class and all its child class?
Any suggestion is highly welcomed. Thanks a lot!
Appendix
For convenience, just post my testing code below. If INDEFINITE_PARAM
defined, it will compile the INDEFINITE_PARAM version, which lead to error as I couldn't figure out how to properly put enable_if
. if INDEFINITE_PARAM
not defined, it can compile the definite param version and gives expected result.
#include <type_traits>
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <string>
//#define INDEFINITE_PARAM
using namespace std;
class BaseA
{
public:
BaseA(int id): m_id(id) {}
virtual void print() const
{
printf("BaseA\n");
}
private:
int m_id;
};
class BaseB
{
public:
BaseB(char id): m_id(id) {}
virtual void print() const
{
printf("BaseB\n");
}
private:
char m_id;
};
class DerivedA1 : public BaseA
{
public:
DerivedA1(int id) : BaseA(id) {}
void print() const override
{
printf("DerivedA1\n");
}
};
class DerivedA2 : public BaseA
{
public:
DerivedA2(int id) : BaseA(id) {}
void print() const override
{
printf("DerivedA2\n");
}
};
class DerivedB : public BaseB
{
public:
DerivedB(char id) : BaseB(id) {}
void print() const override
{
printf("DerivedB\n");
}
};
class C
{
public:
C(string id) : m_id(id) {}
void print() const
{
printf("C\n");
}
private:
string m_id;
};
#ifdef INDEFINITE_PARAM
template<typename T, typename Base, typename ...Args>
class Factory
{
public:
Factory(Args&&... args)
{
m_spInner = std::make_shared<T>(std::forward<Args>(args)...);
}
void print() const
{
printf("%s\n", typeid(T::Base).name());
printf("Factory normal: ");
m_spInner->print();
}
private:
std::shared_ptr<T> m_spInner;
};
template <typename T, typename ...Args>
class Factory<T, BaseA, Args...>
{
public:
Factory(Args&&... args)
{
m_spInner = std::make_shared<T>(std::forward<Args>(args)...);
}
void print() const
{
printf("%s\n", typeid(T::Base).name());
printf("Factory BaseA: ");
m_spInner->print();
}
private:
std::shared_ptr<T> m_spInner;
};
template <typename T, typename... Args>
class Factory<T, typename std::enable_if<std::is_base_of<BaseA, T>::value, BaseA>::type, Args...>
{
public:
Factory(Args&&... args)
{
m_spInner = std::make_shared<T>(std::forward<Args>(args)...);
}
void print() const
{
printf("%s", std::is_base_of<BaseA, T>::value);
printf("Factory BaseA: ");
m_spInner->print();
}
private:
std::shared_ptr<T> m_spInner;
};
/*
template <typename T, typename... Args>
class Factory<T, void, Args...>
{
public:
Factory(Args&&... args)
{
m_spInner = std::make_shared<T>(std::forward<Args>(args)...);
}
void print() const
{
printf("Factory Normal: ");
m_spInner->print();
}
private:
std::shared_ptr<T> m_spInner;
};
*/
#else
template <typename T, typename U, typename Enable = void>
class Factory
{
public:
Factory(U arg)
{
m_spInner = std::make_shared<T>(arg);
}
void print() const
{
printf("Factory normal: ");
m_spInner->print();
}
private:
std::shared_ptr<T> m_spInner;
};
template <typename T, typename U>
class Factory<T, U, typename std::enable_if<std::is_base_of<BaseA, T>::value>::type>
{
public:
Factory(U arg)
{
m_spInner = std::make_shared<T>(arg);
}
void print() const
{
printf("Factory BaseA: ");
m_spInner->print();
}
private:
std::shared_ptr<T> m_spInner;
};
#endif
int main()
{
Factory<DerivedA1, int> factoryA1(1);
factoryA1.print();
Factory<DerivedA2, int> factoryA2(2);
factoryA2.print();
Factory<DerivedB, char> factoryB('B');
factoryB.print();
Factory<C, string> factoryC("C");
factoryC.print();
//Factory<int> factoryInt(1); // compile error
//factoryInt.print();
return 0;
}
Since C++20, you no longer need Enabler template parameter and std::enable_if
/std::void_t
(which indeed is not usable with variadic template for class). You can use constraints and subsumption:
template<typename T, typename ...Args>
class Factory
{
//...
};
template<typename T, typename ...Args>
requires(std::is_base_of<BaseA, T>::value)
class Factory
{
//...
};
Note: In your case, the extra Args
might just be for the constructor:
template <typename T>
requires(std::is_base_of<BaseA, T>::value)
class Factory<T>
{
public:
template <typename... Args>
Factory(Args&&... args)
{
m_spInner = std::make_shared<T>(std::forward<Args>(args)...);
}
// ...
};