Вызов одной из нескольких переменных функций на основе параметров

#c

#c

Вопрос:

Как я могу определить шаблоны Add() ниже так, чтобы вызывались правильные на основе аргументов.

  • Обе функции являются переменными.
  • Add2 всегда имеет 2-й параметр, который является структурой базового BaseB.

 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(Aamp; a, Paramsamp;amp;... args) {
    // ...
}

// Add2
template<typename R, typename A, typename B, typename... Params>
void Add(Aamp; a, Bamp; b, Paramsamp;amp;... args) {
    // ...
}
  

Комментарии:

1. Это не компилируется. Это даже близко не подходит к компиляции. Поэтому невозможно понять, что вы пытаетесь сделать. Пожалуйста, не публикуйте код, который не компилируется.

Ответ №1:

Для C 17 вы можете использовать ответ @Jarod42 с перегруженной Add функцией.

 struct BaseB
{
};
struct B1 : BaseB {};
struct B2 : BaseB {};

template <typename R, typename A, typename ... Params>
void Add1(Aamp; a, Paramsamp;amp;... args)
{
    std::cerr<<"Add1 called"<<std::endl;
}

template <typename R, typename A, typename B, typename ... Params>
void Add2(Aamp; a, Bamp;amp; b, Paramsamp;amp;... args)
{
    std::cerr<<"Add2 called"<<std::endl;
}

template <typename R, typename A>
void Add(Aamp; a)
{
    Add1<R>(a);
}

template<typename R, typename A, typename B, typename... Args>
void Add(Aamp; a, Bamp;amp; b, Argsamp;amp;... 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;
}
  

Для C 14 это можно сделать с помощью SFINAE. Та же реализация также подходит для C 11, просто замените std::enable_if_t и std::decay_t соответствующими заменами.

 struct BaseB
{
};
struct B1 : BaseB {};
struct B2 : BaseB {};

template <typename R, typename A, typename ... Params>
void Add1(Aamp; a, Paramsamp;amp;... args)
{
    std::cerr<<"Add1 called"<<std::endl;
}

template <typename R, typename A, typename B, typename ... Params>
void Add2(Aamp; a, Bamp;amp; b, Paramsamp;amp;... 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(Argsamp;amp;... 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(Argsamp;amp;... args)
    {
        Add2<R>(std::forward<Args>(args)...);
    }
};

template <typename R,typename A>
void Add(Aamp; a)
{
    Add1<R>(a);
}

template <typename R, typename A, typename B, typename ... Args>
void Add(Aamp; a, Bamp;amp; b, Argsamp;amp;... 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;
}
  

Печатает в обоих случаях:

 Add1 called
Add1 called
Add2 called
Add2 called
  

ПРИМЕЧАНИЕ

Сигнатура функции Add(Aamp; a, Bamp; b, Paramsamp;amp;... args) в вашем примере несовместима с вызовом Add<Bar1>(a, B1()) или Add<Bar2>(a, B2(), "string") потому, что вы не можете передать временную переменную в качестве ссылки lvalue для аргумента. Итак, в приведенных выше примерах Bamp;amp; b вместо этого используется. Если вам нужно Bamp; b , вы должны изменить тест так, чтобы использовались ссылки lvalue.

Ответ №2:

С C 17 вы можете сделать

 template<typename R, typename A, typename B, typename... Params>
void Add(Aamp; a, Bamp;amp; b, Paramsamp;amp;... 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(Aamp; a)
{
    // Add1's code
}
  

Прежде чем вы могли бы использовать диспетчеризацию SFINAE / tag.

Отображение диспетчеризации тегов:

 template<typename R, typename A, typename... Params>
void AddImpl(std::false_type, Aamp; a, Paramsamp;amp;... args) {
    // Add1's code
}
template<typename R, typename A, typename B, typename... Params>
void AddImpl(std::true_type, Aamp; a, Bamp;amp; b, Paramsamp;amp;... args) {
    // Add2's code
}

// To handle empty pack
template <typename R, typename A>
void Add(Aamp; a)
{
    AddImpl<R>(std::false_type{}, a); // Add1's code
}

template<typename R, typename A, typename B, typename... Params>
void Add(Aamp; a, Bamp;amp; b, Paramsamp;amp;... args) {
    AddImpl<R>(std::is_base_of<BaseB, std::decay_t<B>>{},
               a,
               std::forward<B>(b),
               std::forward<Params>(ars)...);
}
  

Комментарии:

1. Хорошая попытка, но эта не может справиться Add<Foo1>(a)

2. Действительно, это можно легко исправить с помощью дополнительной перегрузки.