Dess
Dess

Reputation: 2659

Call one of multiple variadic functions based on params

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

Answers (2)

Jarod42
Jarod42

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

Evgeny S.
Evgeny S.

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

Related Questions