Могу ли я перегружать функции с характерными чертами?

Скажем, у меня есть шесть типов, и каждый из них принадлежит к концептуальной категории.
Вот диаграмма, которая показывает это:

Types A, B, and C wrapped inside a box called "Type Category 1" and types D, E, and F wrapped inside a box called "Type Category 2"


Или, возможно, более конкретный пример для вас: Apple, Orange and Banana are all Fruit.  Carrot, Onion, and Cabbage are all Vegetables


Я хочу написать две функции, которые будут обрабатывать все 6 типов.
Типы в "Категории 1" обрабатываются определенным образом, а типы в "Категории 2" обрабатываются по-другому.

Перейдите в код. Во-первых, я создам шесть типов.

//Category 1 Types
class Type_A{};
class Type_B{};
class Type_C{};

//Category 2 Types
class Type_D{};
class Type_E{};
class Type_F{};

Далее я создам два типа, чтобы тип типа можно было обнаружить во время компиляции.

/* Build The Category 1 Type Trait */

//Type_A Type Trait
template <typename T>
struct Is_Type_A {
  static const bool value = false;
};
template <>
struct Is_Type_A<Type_A> {
  static const bool value = true;
};

//Type_B Type Trait
template <typename T>
struct Is_Type_B {
  static const bool value = false;
};
template <>
struct Is_Type_B<Type_B> {
  static const bool value = true;
};

//Type_C Type Trait
template <typename T>
struct Is_Type_C {
  static const bool value = false;
};
template <>
struct Is_Type_C<Type_C> {
  static const bool value = true;
};

//Category 1 Type Trait
template <typename T>
struct Is_Type_From_Category_1 {
  static const bool value = Is_Type_A<T>::value || Is_Type_B<T>::value || Is_Type_C<T>::value;
};

/* Build The Category 2 Type Trait */

//Type_D Type Trait
template <typename T>
struct Is_Type_D {
  static const bool value = false;
};
template <>
struct Is_Type_D<Type_D> {
  static const bool value = true;
};

//Type_E Type Trait
template <typename T>
struct Is_Type_E {
  static const bool value = false;
};
template <>
struct Is_Type_E<Type_E> {
  static const bool value = true;
};

//Type_F Type Trait
template <typename T>
struct Is_Type_F {
  static const bool value = false;
};
template <>
struct Is_Type_F<Type_F> {
  static const bool value = true;
};

//Category 1 Type Trait
template <typename T>
struct Is_Type_From_Category_2 {
  static const bool value = Is_Type_D<T>::value || Is_Type_E<T>::value || Is_Type_F<T>::value;
};

Теперь, когда у меня есть два типа, чтобы отличить, в какую категорию входит каждый из шести типов, я хочу написать две функции. Одна функция будет принимать все из категории 1, а другая функция будет принимать все из категории 2. Есть ли способ сделать это без создания какой-либо функции диспетчеризации? Могу ли я найти способ иметь только две функции; по одному для каждой категории?


EDIT: Я попытался использовать enable_if, как это, но такая попытка приведет к ошибке компилятора.

//Handle all types from Category 1
template<class T ,class = typename std::enable_if<Is_Type_From_Category_1<T>::value>::type >
void function(T t){
    //do category 1 stuff to the type
    return;
}

//Handle all types from Category 2
template<class T ,class = typename std::enable_if<Is_Type_From_Category_2<T>::value>::type >
void function(T t){
    //do category 2 stuff to the type
    return;
}

Изменить 2: Я пробовал код, указанный в ссылке, но это не решение да или нет о том, следует ли вызывать функцию. Это функция, которую я вызываю, учитывая две черты типа. Это будет ошибкой переопределения.

//Handle all types from Category 2
template<class T, class dummy = typename std::enable_if< Is_Type_From_Category_1<T>::value, void>::type>
void function(T t){
    //do category 1 stuff to the type
    return;
}
//Handle all types from Category 2
template<class T, class dummy = typename std::enable_if< Is_Type_From_Category_2<T>::value, void>::type>
void function(T t){
    //do category 2 stuff to the type
    return;
}

Ответ 1

Две сигнатуры функций не могут отличаться только значением по умолчанию параметра шаблона. Что произойдет, если вы явно назовете function< int, void >?

Обычно использование enable_if является возвращаемым типом функции.

//Handle all types from Category 1
template<class T >
typename std::enable_if<Is_Type_From_Category_1<T>::value>::type
function(T t){
    //do category 1 stuff to the type
    return;
}

//Handle all types from Category 2
template<class T >
typename std::enable_if<Is_Type_From_Category_2<T>::value>::type
function(T t){
    //do category 2 stuff to the type
    return;
}

Ответ 2

Я думаю, что использование отправки тегов было бы проще, чем SFINAE.

template<class T>
struct Category;

template<>
struct Category<Type_A> : std::integral_constant<int, 1> {};
template<>
struct Category<Type_B> : std::integral_constant<int, 1> {};
template<>
struct Category<Type_C> : std::integral_constant<int, 1> {};

template<>
struct Category<Type_D> : std::integral_constant<int, 2> {};
template<>
struct Category<Type_E> : std::integral_constant<int, 2> {};
template<>
struct Category<Type_F> : std::integral_constant<int, 2> {};

template<class T>
void foo(std::integral_constant<int, 1>, T x)
{
    // Category 1 types.
}

template<class T>
void foo(std::integral_constant<int, 2>, T x)
{
    // Category 2 types.
}

template<class T>
void foo(T x)
{
    foo(Category<T>(), x);
}

Ответ 3

В качестве альтернативы выбору категории с помощью "признаков" вы также можете рассмотреть CRTP (где тип несет категорию как базу):

template<class Derived> class category1 {};
template<class Derived> class category2 {};

class A1: public category1<A1> { ..... };
class A2: public category2<A2> { ..... };
class B1: public category1<B1> { ..... };
class B2: public category2<B2> { ..... };

template<class T>void funcion_on1(category1<T>& st)
{
   T& t = static_cast<T&>(st);
   .....
}

template<class T>void funcion_on1(category2<T>& st)
{
   T& t = static_cast<T&>(st);
   .....
}

Преимущество состоит в том, чтобы иметь менее загрязненное пространство имен.

Ответ 4

Я узнал следующую технику из R. Martinho Fernandes. Код, показанный ниже, написан для иллюстрации голой проблемы, но вы должны обратиться к этому сообщению в блоге, чтобы получить полный спектр трюков сделайте это довольно.

Вы уже упоминали, что вы сталкиваетесь с проблемами из-за идентичности подписей. Хитрость заключается в том, чтобы типы были разными.

Ваш второй подход близок, но мы не можем использовать void как результирующий тип std::enable_if<>.

Обратите внимание, что следующий код не компилируется, а указание void для std::enable_if<> ничего не меняет, поскольку по умолчанию это void.

#include <iostream>

class A {};
class B {};

template <
    typename T, 
    typename = typename std::enable_if<std::is_same<T, A>::value>::type>
void F(T) {
  std::cout << "A" << std::endl;
}

template <
    typename T, 
    typename = typename std::enable_if<std::is_same<T, B>::value>::type>
void F(T) {
  std::cout << "B" << std::endl;
}

int main() {
  F(A{});
  F(B{});
}

Причина, как вы уже описали, состоит в том, что подписи идентичны. Пусть они дифференцируют их.

#include <iostream>

class A {};
class B {};

template <
    typename T,
    typename std::enable_if<std::is_same<T, A>::value, int>::type = 0>
void F(T) {
  std::cout << "A" << std::endl;
}

template <
    typename T, 
    typename std::enable_if<std::is_same<T, B>::value, int>::type = 0>
void F(T) {
  std::cout << "B" << std::endl;
}

int main() {
  F(A{});
  F(B{});
}

Печать

A
B

Теперь мы дифференцировали типы между двумя функциями, потому что вместо второго параметра шаблона был тип, теперь он int.

Этот подход предпочтительнее использовать std::enable_if<> в возвращаемом типе, например, поскольку конструкторы не имеют типов возврата, шаблон для них не применим.

Примечания: std::is_same<> используется с одним классом для упрощения условия.