6

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);
}
Community
  • 1
  • 1
Yan Zhou
  • 2,481
  • 1
  • 17
  • 33

3 Answers3

9

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.

Matthieu M.
  • 251,718
  • 39
  • 369
  • 642
5

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
iammilind
  • 62,239
  • 27
  • 150
  • 297
  • Thanks for the answer. I figured that much myself. The problem is that I won't know the base case. In fact the base class is a template. The intention is that users may not use my base class but define their own class with compatible interface while part of the interface is optional. Further the users classes may be derived from other bases that I cannot know when writing the library. The workaround now I use is proved an empty tag class that all class inherent from to indicate that it has the optional part as well. And then use is base of. I was just hoping Someone knows something I don't – Yan Zhou Jun 13 '12 at 05:19
  • `The proposed SFINAE works only if the method is public`... Why? – Nawaz Jun 13 '12 at 05:24
  • @Nawaz, [compilation errors out](http://ideone.com/ba1v5) after making `D1::foo()` as `private`. It applies for `protected` methods also. – iammilind Jun 13 '12 at 05:27
  • @iammilind: I was thinking something along this line : http://ideone.com/qIdVN .... (See the added code just above `main()`)... May be this feature can be exploited here? – Nawaz Jun 13 '12 at 05:53
  • @Nawaz, that method may not be useful, because it assumes that the method exists. However, we need to decide if the method exists or not. I tried to specialize your `struct` briefly in SFINAE style, but din't work for me. – iammilind Jun 13 '12 at 06:50
  • Actually, member access must be considered as part of SFINAE in C++11 (see DR1170: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1170). The G++ 4.8 branch supports this as of very recently. Clang has a feature attribute that references this (http://clang.llvm.org/docs/LanguageExtensions.html#cxx_access_control_sfinae) but I'm not sure if it's implemented yet. – porges Sep 21 '12 at 00:29
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.

Tanner Sansbury
  • 48,187
  • 8
  • 101
  • 156