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

前端 未结 3 631
小鲜肉
小鲜肉 2021-01-18 11:11

I found similar questions and answers like this one. However, as I tried out, this SFINAE tests only succeeded if the tested member is directly defined in the class being te

相关标签:
3条回答
  • 2021-01-18 11:53

    There is a way to determine if a class hierachy has a member of a given name. It uses SFINAE and introduces substituation failure in name lookup by creating an ambiguity. Additionally, there is a way to test if public members are callable; however, there is not a way to determine if a member is public with SFINAE.

    Here is an example:

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

    Which produces the following output:

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

    has_foo only checks for the existence of a member named foo. It does not verify that foo is a callable member (a public member function or public member that is a functor).

    can_call_foo checks if T::foo() is callable. If T::foo() is not public, then a compiler error will occur. As far as I know, there is no way to prevent this via SFINAE. For a more complete and brilliant, but fairly complex solution, check here.

    0 讨论(0)
  • 2021-01-18 12:12

    Unfortunately it wouldn't be possible at least in C++03 and I doubt in C++11 also.

    Few important points:

    1. The proposed SFINAE works only if the method is public
    2. Even if the SFINAE would have worked for base methods, the point (1) applies; because for private and protected inheritance the SFINAE may end up useless
    3. Assuming you may want to deal only with public method/inheritance, the code HasFoo::test<> can be enhanced for taking multiple parameters where a base class also can be passed; std::is_base_of<> can be used for further validation of the base/derived relationship; then apply the same logic for base class also
    0 讨论(0)
  • 2021-01-18 12:12

    In C++03, this is unfortunately not possible, sorry.

    In C++11, things get much easier thanks to the magic of decltype. decltype lets you write expressions to deduce the type of their result, so you can perfectly name a member of a base class. And if the method is template, then SFINAE applies to the decltype expression.

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

    Unfortunately ideone has a version of gcc that's too old for this and clang 3.0 is no better.

    0 讨论(0)
提交回复
热议问题