Is it possible to check if a member function is de

2019-02-22 08:49发布

问题:

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 tested. For example the following, class B, D1 print HAS while the other two print NOT HAS. Is there a way to determine that if a class has a member, whether it is defined by itself, or a base class, and the name of the base class is not known in this case. The motivation is that I want to write a generic function that will call a certain method if it exists (from base or not, the type of the parameter is generic, leave along the type of 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);
}

回答1:

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


回答2:

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.



回答3:

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.



标签: c++ sfinae