【问题标题】:Multiple SFINAE rules多个 SFINAE 规则
【发布时间】:2010-04-27 02:41:08
【问题描述】:

看了这个question的答案,了解到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
}

这样的事情可能吗?

谢谢。

【问题讨论】:

    标签: c++ templates sfinae


    【解决方案1】:

    这当然是可能的;您只需要小心确保所有分支都是互斥的,否则您最终会出现歧义。

    看看Boost Type TraitsBoost Enable If,这是支持这一点的两个最佳工具。 Boost ICE(代表 Integral Constant Expression)可用于组合多个类型特征,以帮助您进行更复杂的类型匹配(并确保您的重载是互斥的。

    这可能有点复杂和令人费解,所以这里有一个相对简单的例子。假设你有一个类层次结构:

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

    并且您想为Base 调用函数foo 的一个重载,并为从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&) { } 
    

    但是,如果T 是基类,则is_base_of 返回true,因此如果您尝试调用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--取决于您想用它做什么,您可能可以使某些东西起作用),但总的来说这是可能的。

    【讨论】:

    • 感谢詹姆斯的建议。除了 IS_TEMPLATE_CLASS_C 示例之外,我已经设法使用 enable_if 让所有东西都正常工作。有什么方法可以编写类似于 is_base_of 的东西吗?即如果 T 是 C、C 等,is_template_of::value 将为真,其他一切为假
    • @Fred:问题在于C&lt;X&gt;C&lt;Y&gt; 是不相关的类型。我的第一个想法是将一些 typedef 或整数类型成员添加到名为 C 的类模板 IsInstantiatedTemplateC 并测试它的存在。或者您可以从一个空类BaseC 公开派生类模板C 并测试从该类型的继承。想知道一个类型是否是某个模板的实例化有点奇怪。通常,只要类型实现了某个已知接口,您就不会在意。
    【解决方案2】:

    当你意识到这一点时,问题就很简单了

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

    (显然,打电话时你必须小心,因为选项不是互斥的)

    【讨论】:

      【解决方案3】:

      你实现它的方式,不。 如果 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
      

      根据具体要求,您可以尝试不同的方法。 以上是几个小时前的事情

      【讨论】:

      • 他第二个例子的重点实际上是展示我们想要什么。它不编译,就像第一个没有编译一样。但他在那里提供了一个编译替代方案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-04
      • 2012-10-28
      • 2011-04-07
      相关资源
      最近更新 更多