Несколько правил SFINAE - PullRequest
       9

Несколько правил SFINAE

5 голосов
/ 27 апреля 2010

Прочитав ответ на этот вопрос , я узнал, что SFINAE можно использовать для выбора между двумя функциями в зависимости от того, имеет ли класс определенную функцию-член. Это эквивалентно следующему, просто каждая ветвь в операторе if разделена на перегруженную функцию:

template<typename T>
void Func(T& arg)
{
    if(HAS_MEMBER_FUNCTION_X(T))
        arg.X();
    else
        //Do something else because T doesn't have X()
}

становится

template<typename T>
void Func(T &arg, int_to_type<true>); //T has X()

template<typename T>
void Func(T &arg, int_to_type<false>); //T does not have X()

Мне было интересно, можно ли расширить SFINAE для выполнения нескольких правил. Что-то, что было бы эквивалентно этому:

template<typename T>
void Func(T& arg)
{
    if(HAS_MEMBER_FUNCTION_X(T))                //See if T has a member function X  
        arg.X();
    else if(POINTER_DERIVED_FROM_CLASS_A(T))    //See if T is a pointer to a class derived from class A
        arg->A_Function();              
    else if(DERIVED_FROM_CLASS_B(T))            //See if T derives from class B
        arg.B_Function();
    else if(IS_TEMPLATE_CLASS_C(T))             //See if T is class C<U> where U could be anything
        arg.C_Function();
    else if(IS_POD(T))                          //See if T is a POD type
        //Do something with a POD type
    else
        //Do something else because none of the above rules apply
}

Возможно ли что-то подобное?

Спасибо.

Ответы [ 3 ]

6 голосов
/ 27 апреля 2010

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

Взгляните на Характеристики типа усиления и Boost Enable If , которые являются двумя лучшими инструментами для поддержки этого. Boost ICE (что означает Integral Constant Expression) может использоваться для объединения характеристик нескольких типов, чтобы помочь вам выполнить более сложное сопоставление типов (и гарантировать, что ваши перегрузки взаимоисключающие.

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

struct Base { };
struct Derived : Base { };

и вы хотите вызвать одну перегрузку функции foo для Base и другую перегрузку для любого класса, производного от Base. Первая попытка может выглядеть так:

#include <boost/type_traits.hpp>
#include <boost/utility/enable_if.hpp>

using namespace boost;
using namespace boost::type_traits;

template <typename T>
typename enable_if<is_same<Base, T>, void>::type
foo(const T&) { }

template <typename T>
typename enable_if<is_base_of<Base, T>, void>::type
foo(const T&) { } 

Однако is_base_of возвращает true, если T является базовым классом, поэтому, если вы попытаетесь вызвать foo(Base()), возникает двусмысленность, поскольку оба шаблона функций совпадают. Мы можем решить эту проблему, используя комбинацию черт типа и помощников Boost ICE:

template <typename T>
typename enable_if<is_same<Base, T>, void>::type
foo(const T&) { }

template <typename T>
typename enable_if<
    ice_and<
        is_base_of<Base, T>::value,
        ice_not<is_same<Base, T>::value>::value 
    >, void>::type
foo(const T&) { }

Эти перегрузки являются взаимоисключающими и гарантируют отсутствие двусмысленности.

Некоторые из ваших примеров не поддерживаются (а именно, HAS_MEMBER_FUNCTION_X; я не уверен насчет IS_TEMPLATE_CLASS_C - в зависимости от того, что вы хотите с ним сделать, вы можете заставить что-то работать), но в целом это возможно.

1 голос
/ 27 апреля 2010

Вопрос прост, когда вы понимаете, что

if (a) { X(); }
else if (b) { Y(); }

означает точно так же, как

if (a) { X(); }
if (!a && b) { Y(); }

Однако вы также можете расширить свою true/false дихотомию.

enum FuncVariants { HasMember, PointerDerivedFromA, DerivedFromB, InstanceOfC, isPod }
template<typename T>
void Func(T &arg, int_to_type<HasMember>);

template<typename T>
void Func(T &arg, int_to_type<DerivedFromA>);

template<typename T>
void Func(T &arg, int_to_type<DerivedFromB>);

template<typename T>
void Func(T &arg, int_to_type<InstanceOfC>);

(Очевидно, что при звонке вы должны соблюдать осторожность, так как варианты не являются взаимоисключающими)

1 голос
/ 27 апреля 2010

как у вас это реализовано, нет. Компиляция не удастся, если у arg нет одной из функций. (Я думаю, ты это знаешь, просто убедившись).

Однако это можно сделать, используя специализацию шаблонов (скрытую в магии boost mpl).

Вы можете сделать это когда-нибудь, используя вектор mpl boost с мета-функциями: проверить http://www.boost.org/doc/libs/1_40_0/libs/mpl/doc/refmanual.html

typedefs typename mpl::vector<f0,f1,...>::type handlers; // different handlers
// convert logic to int N to map condition to handler
// can use ternary or bit shift trick
// more general approach could be to use vector of mpl::bool_ and mpl::find

typedef typename mpl::vector_c<bool, (first_condition),
                                     (second_condition),...>::type condition;

typedef typename mpl::find<condition, mpl:: bool_<true> >::type iterator;
typedef typename mpl::at<handlers, iterator::pos::value>::type handler;
handler::apply(...); // call handler with some arguments

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

...