Могу ли я перегружать функции с характерными чертами?
Скажем, у меня есть шесть типов, и каждый из них принадлежит к концептуальной категории.
Вот диаграмма, которая показывает это:
![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<>
используется с одним классом для упрощения условия.