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

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

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

становится

template
void Func(T &arg, int_to_type); //T has X()

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

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

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

Действительно ли что-то вроде этого возможно?

Спасибо.

5
задан Community 23 May 2017 в 10:28
поделиться

3 ответа

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

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

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

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 возвращает истину, если 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 - в зависимости от того, что вы хотите с ним делать, вы можете заставить что-то работать), но в целом это возможно.

6
ответ дан 14 December 2019 в 04:33
поделиться

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

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

вы могли бы когда-нибудь сделать это, используя вектор boost mpl с метафункциями: посмотрите 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

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

1
ответ дан 14 December 2019 в 04:33
поделиться

Вопрос становится простым, если вы понимаете, что

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

означает в точности то же самое, что и

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

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

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
ответ дан 14 December 2019 в 04:33
поделиться
Другие вопросы по тегам:

Похожие вопросы: