【问题标题】:Is it possible to check if a member function is defined for a class even if the member is inherited from an unknown base class即使成员是从未知的基类继承的,是否可以检查是否为类定义了成员函数
【发布时间】:2012-06-13 04:24:37
【问题描述】:

我发现了类似的问题和答案,例如this one。然而,正如我所尝试的,这个 SFINAE 测试只有在被测试的成员被直接定义在被测试的类中时才会成功。例如下面的类BD1打印HAS,而另外两个打印NOT HAS。有没有办法判断一个类是否有成员,是自己定义的,还是基类,这种情况下基类的名字是未知的。动机是我想写一个泛型函数,如果它存在就会调用某个方法(从基与否,参数的类型是泛型的,离开它可能的基的类型)。

#include <iostream>

class HasFoo
{
    public :

    typedef char Small;
    typedef struct {char; char;} Large;

    template <typename C, void (C::*) ()> class SFINAE {};

    template <typename C> static Small test (SFINAE<C, &C::foo> *)
    {
        std::cout << "HAS" << std::endl;
    }

    template <typename C> static Large test (...)
    {
        std::cout << "NOT HAS" << std::endl;
    }
};

class B
{
    public :

    void foo () {}
};

class D1 : public B
{
    public :

    void foo () {} // overide
};

class D2 : public B
{
    public :

    using B::foo;
};

class D3 : public B {};

int main ()
{
    HasFoo::test<B>(0);
    HasFoo::test<D1>(0);
    HasFoo::test<D2>(0);
    HasFoo::test<D3>(0);
}

【问题讨论】:

    标签: c++ sfinae


    【解决方案1】:

    很遗憾,在 C++03 中,这是不可能的,抱歉。

    在 C++11 中,由于decltype 的魔力,事情变得大大变得容易了。 decltype 允许您编写表达式来推断其结果的类型,因此您可以完美地命名基类的成员。如果方法是模板,则 SFINAE 适用于 decltype 表达式。

    #include <iostream>
    
    template <typename T>
    auto has_foo(T& t) -> decltype(t.foo(), bool()) { return true; }
    
    bool has_foo(...) { return false; }
    
    
    struct Base {
        void foo() {}
    };
    
    struct Derived1: Base {
        void foo() {}
    };
    
    struct Derived2: Base {
        using Base::foo;
    };
    
    struct Derived3: Base {
    };
    
    int main() {
        Base b; Derived1 d1; Derived2 d2; Derived3 d3;
    
        std::cout << has_foo(b) << " "
                  << has_foo(d1) << " "
                  << has_foo(d2) << " "
                  << has_foo(d3) << "\n";
    }
    

    不幸的是,ideone 的 gcc 版本太旧了,clang 3.0 也好不到哪里去。

    【讨论】:

      【解决方案2】:

      不幸的是,至少在 C++03 中是不可能的,我也怀疑在 C++11 中。

      几个重点:

      1. 建议的 SFINAE 仅在方法为 public 时才有效
      2. 即使 SFINAE 适用于基本方法,第 (1) 点 适用;因为对于 privateprotected 继承 SFINAE 可能最终没用
      3. 假设您可能只想处理public 方法/继承, 代码HasFoo::test&lt;&gt; 可以增强以获取多个 也可以传递基类的参数; std::is_base_of&lt;&gt; 可用于进一步验证 基础/派生关系;然后对基类应用相同的逻辑 还有

      【讨论】:

      • 感谢您的回答。我自己想了这么多。问题是我不知道基本情况。事实上,基类是一个模板。目的是用户可能不会使用我的基类,而是定义自己的具有兼容接口的类,而部分接口是可选的。此外,用户类可能来自我在编写库时不知道的其他基础。我现在使用的解决方法被证明是一个空标记类,所有类都来自该类,以表明它也具有可选部分。然后用是base的。我只是希望有人知道我不知道的事情
      • The proposed SFINAE works only if the method is public... 为什么?
      • @Nawaz, compilation errors out 在将D1::foo() 设置为private 之后。它也适用于protected 方法。
      • @iammilind:我在想一些事情:ideone.com/qIdVN ....(见main()上面添加的代码)...这个功能可以在这里被利用吗?
      • @Nawaz,该方法可能没有用,因为它假定该方法存在。但是,我们需要确定该方法是否存在。我试图将您的struct 专门化为 SFINAE 风格,但对我不起作用。
      【解决方案3】:

      有一种方法可以确定类层次结构是否具有给定名称的成员。它使用 SFINAE 并通过创建歧义在名称查找中引入替换失败。此外,还有一种方法可以测试公共成员是否可调用;但是,无法确定成员是否在 SFINAE 中公开。

      Here 是一个例子:

      #include <iostream>
      
      template < typename T >
      struct has_foo
      {
        typedef char yes;
        typedef char no[2];
      
        // Type that has a member with the name that will be checked.
        struct fallback { int foo; };
      
        // Type that will inherit from both T and mixin to guarantee that mixed_type
        // has the desired member.  If T::foo exists, then &mixed_type::foo will be
        // ambiguous.  Otherwise, if T::foo does not exists, then &mixed_type::foo
        // will successfully resolve to fallback::foo.
        struct mixed_type: T, fallback {};
      
        template < typename U, U > struct type_check {};
      
        // If substituation does not fail, then &U::foo is not ambiguous, indicating
        // that mixed_type only has one member named foo (i.e. fallback::foo).
        template < typename U > static no&  test( type_check< int (fallback::*),
                                                              &U::foo >* = 0 );
      
        // Substituation failed, so &U::foo is ambiguous, indicating that mixed_type
        // has multiple members named foo.  Thus, T::foo exists.
        template < typename U > static yes& test( ... );
      
        static const bool value = sizeof( yes ) == 
                                  sizeof( test< mixed_type >( NULL ) );
      };
      
      namespace detail {
        class yes {};
        class no{ yes m[2]; };
      
        // sizeof will be used to determine what function is selected given an
        // expression.  An overloaded comma operator will be used to branch based
        // on types at compile-time.
        //   With ( helper, anything-other-than-no, yes ) return yes.
        //   With ( helper, no, yes ) return no.
        struct helper {};
      
        // Return helper.
        template < typename T > helper operator,( helper, const T& ); 
      
        // Overloads.
        yes operator,( helper, yes ); // For ( helper, yes ) return yes.
        no  operator,( helper, no );  // For ( helper, no  ) return no.
        no  operator,( no,     yes ); // For ( no,     yes ) return no.
      } // namespace detail
      
      template < typename T >
      struct can_call_foo
      { 
        struct fallback { ::detail::no foo( ... ) const; };
      
        // Type that will inherit from T and fallback, this guarantees
        // that mixed_type has a foo method.
        struct mixed_type: T, fallback
        {
          using T::foo;
          using fallback::foo;
        };
      
        // U has a foo member.
        template < typename U, bool = has_foo< U >::value >
        struct impl
        {
          // Create the type sequence.
          // - Start with helper to guarantee the custom comma operator is used.
          // - This is evaluationg the expression, not executing, so cast null
          //   to a mixed_type pointer, then invoke foo.  If T::foo is selected,
          //   then the comma operator returns helper.  Otherwise, fooback::foo
          //   is selected, and the comma operator returns no.
          // - Either helper or no was returned from the first comma operator
          //   evaluation.  If ( helper, yes ) remains, then yes will be returned.
          //   Otherwise, ( no, yes ) remains; thus no will be returned. 
          static const bool value = sizeof( ::detail::yes ) == 
                                    sizeof( ::detail::helper(),
                                            ((mixed_type*)0)->foo(),
                                            ::detail::yes() );
        };
      
        // U does not have a 'foo' member.
        template < typename U >
        struct impl< U, false >
        {
          static const bool value = false;
        };
      
        static const bool value = impl< T >::value;
      };
      
      // Types containing a foo member function.
      struct B     { void foo();   };
      struct D1: B { bool foo();   }; // hide B::foo
      struct D2: B { using B::foo; }; // no-op, as no hiding occured.
      struct D3: B {               }; 
      
      // Type that do not have a member foo function.
      struct F {};
      
      // Type that has foo but it is not callable via T::foo().
      struct G  { int foo;         };
      struct G1 { bool foo( int ); };
      
      int main ()
      {
        std::cout << "B:  " << has_foo< B  >::value << " - "
                            << can_call_foo< B >::value << "\n"
                  << "D1: " << has_foo< D1 >::value << " - "
                            << can_call_foo< D1 >::value << "\n"
                  << "D2: " << has_foo< D2 >::value << " - "
                            << can_call_foo< D2 >::value << "\n"
                  << "D3: " << has_foo< D3 >::value << " - "
                            << can_call_foo< D3 >::value << "\n"
                  << "F:  " << has_foo< F  >::value << " - "
                            << can_call_foo< F >::value << "\n"
                  << "G:  " << has_foo< G  >::value << " - "
                            << can_call_foo< G >::value << "\n"
                  << "G1: " << has_foo< G1  >::value << " - "
                            << can_call_foo< G1 >::value << "\n"
                  << std::endl;
        return 0;
      }
      

      产生以下输出:

      B: 1 - 1
      D1:1 - 1
      D2:1 - 1
      D3:1 - 1
      F: 0 - 0
      克:1 - 0
      G1:1 - 0

      has_foo 只检查名为foo 的成员是否存在。它不验证foo 是否为可调用成员(公共成员函数或作为函子的公共成员)。

      can_call_foo 检查T::foo() 是否可调用。如果T::foo() 不公开,则会发生编译器错误。据我所知,没有办法通过 SFINAE 阻止这种情况。如需更完整、更出色但相当复杂的解决方案,请查看here

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-06-16
        • 1970-01-01
        • 2020-05-27
        • 1970-01-01
        相关资源
        最近更新 更多