2013-12-20 3 views
11

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

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; 
} 

Edit 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; 
} 
+0

Возможный дубликат [Общие сведения о SFINAE] (http://stackoverflow.com/questions/17829874/understanding-sfinae) – Potatoswatter

+0

@Potatoswatter правильно, но я не думаю, что могу перегружать с характерной чертой? Сначала они должны быть обнаружены истинными или ложными; что означало бы, что я должен отправить? –

+0

См. Связанные вопросы и ответы. Действительно ли 'enable_if' не совсем то, что вы ищете? – Potatoswatter

ответ

10

Подписи двух функций не могут отличаться только значением по умолчанию параметра шаблона. Что произойдет, если вы явно назовете 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; 
} 
+0

Если типы возврата отличаются друг от друга, почему обе функции возвращают void? –

+3

@TrevorHickey 'enable_if' имеет необязательный второй аргумент шаблона для возвращаемого типа, который по умолчанию равен' void'. – Potatoswatter

9

Я думаю, что использование дескриптора меток было бы проще, чем 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); 
} 
+1

'template struct Категория : std :: integral_constant {}; 'Вы хотите использовать полную специализацию? – Constructor

+0

@Constructor да, спасибо за определение его. – Simple

+0

Я думаю, что это можно сделать немного более чистым и понятным, заменив «integ_constant's» тегами типов «Категория1» и «Категория2» и удалив лишние комментарии. – Casey

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); 
    ..... 
} 

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

+0

Это не сработает, потому что ссылка на специализированную специализацию не будет связывать и вывести аргументы шаблона из подобъекта базового класса. – Potatoswatter

+0

Конечно, & может быть const & or && в зависимости от того, с чем вам нужно привязываться. У меня есть тонны библиотек, которые работают так, так что - не беспокойтесь - не раздражайтесь предрассудками! Там нет никакой специализированной специализации. Просто чистый вывод.Я действительно не вижу в этом кодеке то, о чем вы говорите –

3

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

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

Ваш второй подход близок, но мы не можем использовать 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 

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

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

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

+0

Манекен 'int' может мешать, особенно с пакетом параметров. Почему бы не следовать SFINAE по типу возвращаемого типа? – Potatoswatter

+0

Точка, описанная в ответе для предпочтения этого метода, заключается в том, что идиома возвращаемого типа не может быть применена к конструкторам. Однако вы хорошо понимаете, что этот шаблон не работает с параметрами вариационного шаблона. Возможно, оба подхода могут быть рассмотрены, или, возможно, существует шаблон, который может применяться во всех случаях. – mpark

+0

Существует еще одно место, где можно вставить SFINAE, в спецификации исключения как «noexcept (SFINAE)». И неиспользуемые аргументы функции также являются крюком. Однако распространенное использование - использовать тип возврата по умолчанию, как это было сделано с C++ 98. – Potatoswatter