7

私はこのような同様の質問と回答を見つけました。ただし、試してみたところ、このSFINAEテストは、テスト対象のメンバーがテスト対象のクラスで直接定義されている場合にのみ成功しました。たとえば、次のclassBD1printHASで、他の2つはprintNOT HASです。クラスにメンバーがあるかどうか、それがそれ自体で定義されているか、基本クラスであるかを判断する方法はありますか?この場合、基本クラスの名前は不明です。動機は、特定のメソッドが存在する場合にそれを呼び出すジェネリック関数を作成したいということです(ベースからかどうかにかかわらず、パラメーターのタイプはジェネリックであり、可能なベースのタイプはそのままにしておきます)。

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

3 に答える 3

9

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があり、clang3.0はこれ以上のものではありません。

于 2012-06-13T09:28:30.693 に答える
5

残念ながら、少なくともC ++ 03では不可能であり、C++11でも疑わしいです。

いくつかの重要なポイント:

  1. 提案されたSFINAEは、メソッドがpublic
  2. SFINAEが基本メソッドで機能したとしても、ポイント(1)が適用されます。SFINAEはprivate、継承すると役に立たなくなる可能性があるためです。protected
  3. publicメソッド/継承のみを処理したい場合はHasFoo::test<>、基本クラスも渡すことができる複数のパラメーターを取得するようにコードを拡張できます。 std::is_base_of<>基本/派生関係のさらなる検証に使用できます。次に、基本クラスにも同じロジックを適用します
于 2012-06-13T05:13:01.187 に答える
3

クラス階層に特定の名前のメンバーがあるかどうかを判断する方法があります。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。という名前のメンバーの存在のみをチェックしますfoofoo呼び出し可能なメンバー(パブリックメンバー関数またはファンクターであるパブリックメンバー)であることは確認されません。

can_call_fooT::foo()呼び出し可能かどうかを確認します。が公開されていない場合T::foo()、コンパイラエラーが発生します。私の知る限り、SFINAEを介してこれを防ぐ方法はありません。より完全で優れた、しかしかなり複雑なソリューションについては、こちらを確認してください。

于 2012-06-29T06:15:33.240 に答える