Reputation: 2659
How can I defined the Add() templates below such that the correct ones get called, based on the arguments.
struct BaseB {};
struct B1 : BaseB {};
struct B2 : BaseB {};
Add<Foo1>(a); // Call Add1
Add<Foo2>(a, "string"); // Call Add1
Add<Bar1>(a, B1()); // Call Add2
Add<Bar2>(a, B2(), "string"); // Call Add2
// Add1
template<typename R, typename A, typename... Params>
void Add(A& a, Params&&... args) {
// ...
}
// Add2
template<typename R, typename A, typename B, typename... Params>
void Add(A& a, B& b, Params&&... args) {
// ...
}
Upvotes: 0
Views: 88
Reputation: 217275
With C++17, you might do
template<typename R, typename A, typename B, typename... Params>
void Add(A& a, B&& b, Params&&... args) {
if constexpr (std::is_base_of<BaseB, std::decay_t<B>>::value) {
// Add2's code
} else {
// Add1's code
}
}
// To handle empty pack
template <typename R, typename A>
void Add(A& a)
{
// Add1's code
}
Before you might use SFINAE/tag dispatching.
Showing tag dispatching:
template<typename R, typename A, typename... Params>
void AddImpl(std::false_type, A& a, Params&&... args) {
// Add1's code
}
template<typename R, typename A, typename B, typename... Params>
void AddImpl(std::true_type, A& a, B&& b, Params&&... args) {
// Add2's code
}
// To handle empty pack
template <typename R, typename A>
void Add(A& a)
{
AddImpl<R>(std::false_type{}, a); // Add1's code
}
template<typename R, typename A, typename B, typename... Params>
void Add(A& a, B&& b, Params&&... args) {
AddImpl<R>(std::is_base_of<BaseB, std::decay_t<B>>{},
a,
std::forward<B>(b),
std::forward<Params>(ars)...);
}
Upvotes: 1
Reputation: 858
For C++17 you can use @Jarod42's answer with overloaded Add
function.
struct BaseB
{
};
struct B1 : BaseB {};
struct B2 : BaseB {};
template <typename R, typename A, typename ... Params>
void Add1(A& a, Params&&... args)
{
std::cerr<<"Add1 called"<<std::endl;
}
template <typename R, typename A, typename B, typename ... Params>
void Add2(A& a, B&& b, Params&&... args)
{
std::cerr<<"Add2 called"<<std::endl;
}
template <typename R, typename A>
void Add(A& a)
{
Add1<R>(a);
}
template<typename R, typename A, typename B, typename... Args>
void Add(A& a, B&& b, Args&&... args) {
if constexpr (std::is_base_of<BaseB, std::decay_t<B>>::value) {
Add2<R>(a,std::forward<B>(b),std::forward<Args>(args)...);
} else {
Add1<R>(a,std::forward<B>(b),std::forward<Args>(args)...);
}
}
// try it
struct Foo1{};
struct Foo2{};
struct Bar1{};
struct Bar2{};
int main()
{
int a;
Add<Foo1>(a); // Call Add1
Add<Foo2>(a, "string"); // Call Add1
Add<Bar1>(a, B1()); // Call Add2
Add<Bar2>(a, B2(), "string"); // Call Add2
return 0;
}
For C++14 it can be done using SFINAE. The same implementation is also suitable for C++11, just replace std::enable_if_t
and std::decay_t
with corresponding substitutions.
struct BaseB
{
};
struct B1 : BaseB {};
struct B2 : BaseB {};
template <typename R, typename A, typename ... Params>
void Add1(A& a, Params&&... args)
{
std::cerr<<"Add1 called"<<std::endl;
}
template <typename R, typename A, typename B, typename ... Params>
void Add2(A& a, B&& b, Params&&... args)
{
std::cerr<<"Add2 called"<<std::endl;
}
template <typename R, typename T1, typename T2, typename Enable=void>
struct AddImpl
{};
template <typename R, typename T1, typename T2>
struct AddImpl<R,T1,T2,
std::enable_if_t<!std::is_base_of<BaseB, std::decay_t<T2>>::value>
>
{
template <typename ... Args>
static void invoke(Args&&... args)
{
Add1<R>(std::forward<Args>(args)...);
}
};
template <typename R, typename T1, typename T2>
struct AddImpl<R,T1,T2,
std::enable_if_t<std::is_base_of<BaseB, std::decay_t<T2>>::value>
>
{
template <typename ... Args>
static void invoke(Args&&... args)
{
Add2<R>(std::forward<Args>(args)...);
}
};
template <typename R,typename A>
void Add(A& a)
{
Add1<R>(a);
}
template <typename R, typename A, typename B, typename ... Args>
void Add(A& a, B&& b, Args&&... args)
{
AddImpl<R,A,B>::invoke(a,std::forward<B>(b),std::forward<Args>(args)...);
}
// try it
struct Foo1{};
struct Foo2{};
struct Bar1{};
struct Bar2{};
int main()
{
int a;
Add<Foo1>(a); // Call Add1
Add<Foo2>(a, "string"); // Call Add1
Add<Bar1>(a, B1()); // Call Add2
Add<Bar2>(a, B2(), "string"); // Call Add2
return 0;
}
Prints in both cases:
Add1 called
Add1 called
Add2 called
Add2 called
NOTE
Signature of function Add(A& a, B& b, Params&&... args)
in your example is not compatible with calling Add<Bar1>(a, B1())
or Add<Bar2>(a, B2(), "string")
because you cannot pass temporary variable as lvalue reference for the argument. So, in the examples above B&& b
is used instead. If you need B& b
then you must change the test so that the lvalue references are used.
Upvotes: 1