是否可以检查是否为类定义了成员函数,即使该成员继承自未知基类

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

本文关键字:是否 成员 继承 基类 未知 函数 检查 定义      更新时间:2023-10-16

我发现了类似的问题和答案。但是,正如我所尝试的那样,只有在被测试的类中直接定义被测试的成员时,此 SFINAE 测试才会成功。例如,类 B D1打印HAS,而其他两个打印NOT HAS。有没有办法确定一个类是否有成员,它是由它自己定义的,还是一个基类,在这种情况下基类的名称是未知的。动机是我想编写一个泛型函数,如果某个方法存在,它将调用某个方法(无论是否从 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);
}

在 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也好不到哪里去。

不幸的是,

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

几个要点:

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

有一种方法可以确定类层次结构是否具有给定名称的成员。 它使用 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;
}

这将产生以下输出:

乙: 1 - 1D1: 1 - 1D2: 1 - 1D3: 1 - 1女: 0 - 0金: 1 - 0G1: 1 - 0

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

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