Is it possible to check if a member function is defined for a class, even if the element inherits from an unknown base class

I found similar questions and answers, like this one . However, as I tried, these SFINAE tests only run if the item being tested is directly defined in the tested class. For example, print the next class B, and D1print the HASother two NOT HAS. Is there a way to determine if a class has a member, regardless of whether it defines itself or the base class, and the name of the base class is unknown in this case. The motivation is that I want to write a general function that will call a specific method if it exists (from the base or not, the type of the parameter is general, leave it as its possible base).

#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);
}
+5
source share
3 answers

, , , ++ 03, ++ 11.

:

  • SFINAE , public
  • SFINAE , (1) ; private protected SFINAE
  • , public/inheritance, HasFoo::test<> , ; std::is_base_of<> / ;
+3

++ 03 , , , .

++ 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 .

+8

, hierachy . SFINAE , . , , ; , SFINAE.

:

#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
G:  1 - 0
G1: 1 - 0

has_foo foo. , foo (public member function public member, ).

can_call_foochecks if T::foo()called. If T::foo()it is not publicly available, a compiler error will occur. As far as I know, there is no way to prevent this through SFINAE. For a more complete and brilliant, but rather complex solution, here .

+3
source

All Articles