31

SFINAEを使用すると、特定のクラスに特定のメンバー関数があるかどうかを検出できます。しかし、継承されたメンバー関数をテストしたい場合はどうなりますか?

以下は、VC8およびGCC4では機能しません(つまりA、メンバー関数を持っているが、それを継承しfoo()ていないことを検出します)。B

#include <iostream>

template<typename T, typename Sig>                                 
struct has_foo {                     
    template <typename U, U> struct type_check;
    template <typename V> static char (& chk(type_check<Sig, &V::foo>*))[1];
    template <typename  > static char (& chk(...))[2]; 
    static bool const value = (sizeof(chk<T>(0)) == 1);
};

struct A {
    void foo();
};

struct B : A {};

int main()
{
    using namespace std;
    cout << boolalpha << has_foo<A, void (A::*)()>::value << endl; // true
    cout << boolalpha << has_foo<B, void (B::*)()>::value << endl; // false
}

では、継承されたメンバー関数をテストする方法はありますか?

4

4 に答える 4

30

このスレッドを見てください:

http://lists.boost.org/boost-users/2009/01/44538.php

その議論にリンクされているコードから派生:

#include <iostream>

template <typename Type> 
class has_foo
{ 
   class yes { char m;}; 
   class no { yes m[2];}; 
   struct BaseMixin 
   { 
     void foo(){} 
   }; 
   struct Base : public Type, public BaseMixin {}; 
   template <typename T, T t>  class Helper{}; 
   template <typename U> 
   static no deduce(U*, Helper<void (BaseMixin::*)(), &U::foo>* = 0); 
   static yes deduce(...); 
public: 
   static const bool result = sizeof(yes) == sizeof(deduce((Base*)(0))); 
}; 

struct A {
    void foo();
};

struct B : A {};

struct C {};

int main()
{
    using namespace std;
    cout << boolalpha << has_foo<A>::result << endl;
    cout << boolalpha << has_foo<B>::result << endl;
    cout << boolalpha << has_foo<C>::result;
}

結果:

true
true
false
于 2009-12-27T22:10:59.520 に答える
23

ジョシュペリーの答えは非常に巧妙でエレガントですが、(投稿の下に記載されているように) foo() の署名を適切にチェックせず、基本的な型 (int など) では機能しません: コンパイラ エラーが発生します。継承されたメンバーを正しく処理し、メンバー関数の署名もチェックする手法を提案します。詳細に入る代わりに、2 つの例を挙げて、コードがそれ自体で語ってくれることを願っています。

例1:

次の署名を持つメンバーを確認しています: T::const_iterator begin() const

template<class T> struct has_const_begin
{
    typedef char (&Yes)[1];
    typedef char (&No)[2];

    template<class U> 
    static Yes test(U const * data, 
                    typename std::enable_if<std::is_same<
                             typename U::const_iterator, 
                             decltype(data->begin())
                    >::value>::type * = 0);
    static No test(...);
    static const bool value = sizeof(Yes) == sizeof(has_const_begin::test((typename std::remove_reference<T>::type*)0));
};

メソッドの constness もチェックし、プリミティブ型でも機能することに注意してください。(has_const_begin<int>::valueつまり、 false であり、コンパイル時エラーは発生しません。)

例 2

今、私たちは署名を探しています:void foo(MyClass&, unsigned)

template<class T> struct has_foo
{
    typedef char (&Yes)[1];
    typedef char (&No)[2];

    template<class U>
    static Yes test(U * data, MyClass* arg1 = 0,
                    typename std::enable_if<std::is_void<
                             decltype(data->foo(*arg1, 1u))
                    >::value>::type * = 0);
    static No test(...);
    static const bool value = sizeof(Yes) == sizeof(has_foo::test((typename std::remove_reference<T>::type*)0));
};

MyClass は、デフォルトで構築可能である必要も、特別な概念を満たす必要もないことに注意してください。この手法は、テンプレート メンバーでも機能します。

これに関する意見をお待ちしております。

于 2012-01-06T03:22:24.617 に答える
17

ここにいくつかの使用法のスニペットがあります:

x特定のクラスのメンバーを確認します。var、func、class、union、または enum の可能性があります。

CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;

メンバー関数を確認しますvoid x()

//Func signature MUST have T as template variable here... simpler this way :\
CREATE_MEMBER_FUNC_SIG_CHECK(x, void (T::*)(), void__x);
bool has_func_sig_void__x = has_member_func_void__x<class_to_check_for_x>::value;

メンバー変数を確認しますx

CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;

メンバー クラスを確認しxます。

CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;

メンバー ユニオンを確認しxます。

CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;

メンバーの列挙型を確認しxます。

CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;

x署名に関係なく、メンバー関数を確認します。

CREATE_MEMBER_CHECK(x);
CREATE_MEMBER_VAR_CHECK(x);
CREATE_MEMBER_CLASS_CHECK(x);
CREATE_MEMBER_UNION_CHECK(x);
CREATE_MEMBER_ENUM_CHECK(x);
CREATE_MEMBER_FUNC_CHECK(x);
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;

また

CREATE_MEMBER_CHECKS(x);  //Just stamps out the same macro calls as above.
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;

詳細とコア:

/*
    - Multiple inheritance forces ambiguity of member names.
    - SFINAE is used to make aliases to member names.
    - Expression SFINAE is used in just one generic has_member that can accept
      any alias we pass it.
*/

//Variadic to force ambiguity of class members.  C++11 and up.
template <typename... Args> struct ambiguate : public Args... {};

//Non-variadic version of the line above.
//template <typename A, typename B> struct ambiguate : public A, public B {};

template<typename A, typename = void>
struct got_type : std::false_type {};

template<typename A>
struct got_type<A> : std::true_type {
    typedef A type;
};

template<typename T, T>
struct sig_check : std::true_type {};

template<typename Alias, typename AmbiguitySeed>
struct has_member {
    template<typename C> static char ((&f(decltype(&C::value))))[1];
    template<typename C> static char ((&f(...)))[2];

    //Make sure the member name is consistently spelled the same.
    static_assert(
        (sizeof(f<AmbiguitySeed>(0)) == 1)
        , "Member name specified in AmbiguitySeed is different from member name specified in Alias, or wrong Alias/AmbiguitySeed has been specified."
    );

    static bool const value = sizeof(f<Alias>(0)) == 2;
};

マクロ (El Diablo!):

CREATE_MEMBER_CHECK:

//Check for any member with given name, whether var, func, class, union, enum.
#define CREATE_MEMBER_CHECK(member)                                         \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct Alias_##member;                                                      \
                                                                            \
template<typename T>                                                        \
struct Alias_##member <                                                     \
    T, std::integral_constant<bool, got_type<decltype(&T::member)>::value>  \
> { static const decltype(&T::member) value; };                             \
                                                                            \
struct AmbiguitySeed_##member { char member; };                             \
                                                                            \
template<typename T>                                                        \
struct has_member_##member {                                                \
    static const bool value                                                 \
        = has_member<                                                       \
            Alias_##member<ambiguate<T, AmbiguitySeed_##member>>            \
            , Alias_##member<AmbiguitySeed_##member>                        \
        >::value                                                            \
    ;                                                                       \
}

CREATE_MEMBER_VAR_CHECK:

//Check for member variable with given name.
#define CREATE_MEMBER_VAR_CHECK(var_name)                                   \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct has_member_var_##var_name : std::false_type {};                      \
                                                                            \
template<typename T>                                                        \
struct has_member_var_##var_name<                                           \
    T                                                                       \
    , std::integral_constant<                                               \
        bool                                                                \
        , !std::is_member_function_pointer<decltype(&T::var_name)>::value   \
    >                                                                       \
> : std::true_type {}

CREATE_MEMBER_FUNC_SIG_CHECK:

//Check for member function with given name AND signature.
#define CREATE_MEMBER_FUNC_SIG_CHECK(func_name, func_sig, templ_postfix)    \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct has_member_func_##templ_postfix : std::false_type {};                \
                                                                            \
template<typename T>                                                        \
struct has_member_func_##templ_postfix<                                     \
    T, std::integral_constant<                                              \
        bool                                                                \
        , sig_check<func_sig, &T::func_name>::value                         \
    >                                                                       \
> : std::true_type {}

CREATE_MEMBER_CLASS_CHECK:

//Check for member class with given name.
#define CREATE_MEMBER_CLASS_CHECK(class_name)               \
                                                            \
template<typename T, typename = std::true_type>             \
struct has_member_class_##class_name : std::false_type {};  \
                                                            \
template<typename T>                                        \
struct has_member_class_##class_name<                       \
    T                                                       \
    , std::integral_constant<                               \
        bool                                                \
        , std::is_class<                                    \
            typename got_type<typename T::class_name>::type \
        >::value                                            \
    >                                                       \
> : std::true_type {}

CREATE_MEMBER_UNION_CHECK:

//Check for member union with given name.
#define CREATE_MEMBER_UNION_CHECK(union_name)               \
                                                            \
template<typename T, typename = std::true_type>             \
struct has_member_union_##union_name : std::false_type {};  \
                                                            \
template<typename T>                                        \
struct has_member_union_##union_name<                       \
    T                                                       \
    , std::integral_constant<                               \
        bool                                                \
        , std::is_union<                                    \
            typename got_type<typename T::union_name>::type \
        >::value                                            \
    >                                                       \
> : std::true_type {}

CREATE_MEMBER_ENUM_CHECK:

//Check for member enum with given name.
#define CREATE_MEMBER_ENUM_CHECK(enum_name)                 \
                                                            \
template<typename T, typename = std::true_type>             \
struct has_member_enum_##enum_name : std::false_type {};    \
                                                            \
template<typename T>                                        \
struct has_member_enum_##enum_name<                         \
    T                                                       \
    , std::integral_constant<                               \
        bool                                                \
        , std::is_enum<                                     \
            typename got_type<typename T::enum_name>::type  \
        >::value                                            \
    >                                                       \
> : std::true_type {}

CREATE_MEMBER_FUNC_CHECK:

//Check for function with given name, any signature.
#define CREATE_MEMBER_FUNC_CHECK(func)          \
template<typename T>                            \
struct has_member_func_##func {                 \
    static const bool value                     \
        = has_member_##func<T>::value           \
        && !has_member_var_##func<T>::value     \
        && !has_member_class_##func<T>::value   \
        && !has_member_union_##func<T>::value   \
        && !has_member_enum_##func<T>::value    \
    ;                                           \
}

CREATE_MEMBER_CHECKS:

//Create all the checks for one member.  Does NOT include func sig checks.
#define CREATE_MEMBER_CHECKS(member)    \
CREATE_MEMBER_CHECK(member);            \
CREATE_MEMBER_VAR_CHECK(member);        \
CREATE_MEMBER_CLASS_CHECK(member);      \
CREATE_MEMBER_UNION_CHECK(member);      \
CREATE_MEMBER_ENUM_CHECK(member);       \
CREATE_MEMBER_FUNC_CHECK(member)
于 2011-06-12T21:58:27.640 に答える
5

std::declvalすべての答えが複雑すぎるように見えるので、 and (GCC 4.8.3)を使用した独自のソリューションを紹介したいと思います。std::enable_if

#define MEMBER_FUNC_CHECKER(name, fn, ret, args) \
template<class C, typename=void> struct name : std::false_type {}; \
template<class C> struct name<C, typename std::enable_if< \
  std::is_convertible<decltype(std::declval<C>().fn args), ret \
  >::value>::type> : std::true_type {};

注: 署名の正確なチェックではなく、変換可能な戻り値の型を持つ呼び出し可能な関数のチェックです。(編集: からis_sameに変更is_convertible)

テスト

struct One {
    int get() { return 0; }
    int add(int x, int y) { return x+y; }
};
struct Two: One {};
struct Not {};

MEMBER_FUNC_CHECKER(has_get, get, int, ())
MEMBER_FUNC_CHECKER(has_add, add, int, (1,2))

int main() {
    cout << "One " << (has_get<One>() ? "has" : "does not have")
        << " int get()" << endl;
    cout << "Two " << (has_get<Two>() ? "has" : "does not have")
        << " int get()" << endl;
    cout << "Not " << (has_get<Not>() ? "has" : "does not have")
        << " int get()" << endl;
    cout << "One " << (has_add<One>() ? "has" : "does not have")
        << " int add(int, int)" << endl;
    cout << "Two " << (has_add<Two>() ? "has" : "does not have")
        << " int add(int, int)" << endl;
    cout << "Not " << (has_add<Not>() ? "has" : "does not have")
        << " int add(int, int)" << endl;
    cout << "int " << (has_get<int>() ? "has" : "does not have")
        << " int get()" << endl;
}

出力

1 つは int get() を持っています
2 つは int get() を持っています
int get() を持たない
1つはint add(int, int)
2 つは int add(int, int) を持っています
int add(int, int) を持たない
int には int get() がありません

更新: 私のチェッカー

/// Checker for typedef with given name and convertible type
#define TYPEDEF_CHECKER(checker, name) \
template<class C, typename T, typename = void> struct checker : std::false_type {}; \
template<class C, typename T> struct checker<C, T, typename std::enable_if< \
  std::is_convertible<typename C::name, T>::value>::type> : std::true_type {}
/// Checker for typedef with given name and exact type
#define TYPEDEF_CHECKER_STRICT(checker, name) \
template<class C, typename T, typename = void> struct checker : std::false_type {}; \
template<class C, typename T> struct checker<C, T, typename std::enable_if< \
  std::is_same<typename C::name, T>::value>::type> : std::true_type {}
/// Checker for typedef with given name and any type
#define TYPEDEF_CHECKER_ANY(checker, name) \
template<class C, typename = void> struct checker : std::false_type {}; \
template<class C> struct checker<C, typename std::enable_if< \
  !std::is_same<typename C::name*, void>::value>::type> : std::true_type {}

/// Checker for member with given name and convertible type
#define MTYPE_CHECKER(checker, name) \
template<class C, typename T, typename = void> struct checker : std::false_type {}; \
template<class C, typename T> struct checker<C, T, typename std::enable_if< \
  std::is_convertible<decltype(C::name), T>::value>::type> : std::true_type {}
/// Checker for member with given name and exact type
#define MTYPE_CHECKER_STRICT(checker, name) \
template<class C, typename T, typename = void> struct checker : std::false_type {}; \
template<class C, typename T> struct checker<C, T, typename std::enable_if< \
  std::is_same<decltype(C::name), T>::value>::type> : std::true_type {}
/// Checker for member with given name and any type
#define MTYPE_CHECKER_ANY(checker, name) \
template<class C, typename = void> struct checker : std::false_type {}; \
template<class C> struct checker<C, typename std::enable_if< \
  !std::is_same<decltype(C::name)*, void>::value>::type> : std::true_type {}

/// Checker for static const variable with given name and value
#define MVALUE_CHECKER(checker, name, val) \
template<class C, typename = void> struct checker : std::false_type {}; \
template<class C> struct checker<C, typename std::enable_if< \
  std::is_convertible<decltype(C::name), const decltype(val)>::value && C::name == val>::type> : std::true_type {}
/// Checker for static const variable with given name, value and type
#define MVALUE_CHECKER_STRICT(checker, name, val) \
template<class C, typename = void> struct checker : std::false_type {}; \
template<class C> struct checker<C, typename std::enable_if< \
  std::is_same<decltype(C::name), const decltype(val)>::value && C::name == val>::type> : std::true_type {}

/// Checker for member function with convertible return type and accepting given arguments
#define METHOD_CHECKER(checker, name, ret, args) \
template<class C, typename=void> struct checker : std::false_type {}; \
template<class C> struct checker<C, typename std::enable_if< \
  std::is_convertible<decltype(std::declval<C>().name args), ret>::value>::type> : std::true_type {};
/// Checker for member function with exact retutn type and accepting given arguments
#define METHOD_CHECKER_STRICT_RET(name, fn, ret, args) \
template<class C, typename=void> struct name : std::false_type {}; \
template<class C> struct name<C, typename std::enable_if< \
  std::is_same<decltype(std::declval<C>().fn args), ret>::value>::type> : std::true_type {};
/// Checker for member function accepting given arguments
#define METHOD_CHECKER_ANY(name, fn, args) \
template<class C, typename=void> struct name : std::false_type {}; \
template<class C> struct name<C, typename std::enable_if< \
  !std::is_same<decltype(std::declval<C>().fn args)*, void>::value>::type> : std::true_type {};

テストコード

struct One {
    typedef int type;
    static constexpr bool v = true;
    type x;
    One(type x = 0): x(x) {}
    ~One() {}
    type get() { return x; }
    type add(type x, type y) { return x+y; }
};
struct Two: One {};
struct Not {};

TYPEDEF_CHECKER(has_type, type);
TYPEDEF_CHECKER_ANY(any_type, type);
TYPEDEF_CHECKER_STRICT(exact_type, type);
MTYPE_CHECKER(has_x, x);
MTYPE_CHECKER_ANY(any_x, x);
MTYPE_CHECKER_STRICT(exact_x, x);
MVALUE_CHECKER(true_v, v, true);
MVALUE_CHECKER(true_z, z, true);
MVALUE_CHECKER(false_v, v, false);
MVALUE_CHECKER(one_v, v, 1);
MVALUE_CHECKER_STRICT(exact_v, v, 1);
METHOD_CHECKER(has_get, get, long, ());
METHOD_CHECKER(has_add, add, long, (1,2))
METHOD_CHECKER_ANY(any_get, get, ());
METHOD_CHECKER_STRICT_RET(int_get, get, int, ())
METHOD_CHECKER_STRICT_RET(long_get, get, long, ())

int main() {
#define CHECK(name, desc, ...) cout << endl; \
    cout << "One " << (name<One, ##__VA_ARGS__>() ? "has " : "does not have ") << desc << endl; \
    cout << "Two " << (name<Two, ##__VA_ARGS__>() ? "has " : "does not have ") << desc << endl; \
    cout << "Not " << (name<Not, ##__VA_ARGS__>() ? "has " : "does not have ") << desc << endl; \
    cout << "int " << (name<int, ##__VA_ARGS__>() ? "has " : "does not have ") << desc << endl
    string sep = string(60, '-');
    cout << sep;
    CHECK(any_type, "typedef type");
    CHECK(has_type, "typedef type convertible to long", long);
    CHECK(exact_type, "typedef type = int", int);
    CHECK(exact_type, "typedef type = long", long);

    cout << sep;
    CHECK(any_x, "var x");
    CHECK(has_x, "var x of type convertible to long", long);
    CHECK(exact_x, "var x of type int", int);
    CHECK(exact_x, "var x of type long", long);

    cout << sep;
    CHECK(true_v, "var v with value equal to true");
    CHECK(true_z, "var z with value equal to true");
    CHECK(false_v, "var v with value equal to false");
    CHECK(one_v, "var v with value equal to 1");
    CHECK(exact_v, "var v with value equal to 1 of type int");

    cout << sep;
    CHECK(has_get, "get()");
    CHECK(has_get, "get() with return type covertible to long");
    CHECK(has_add, "add() accepting two ints and returning ~ long");
    CHECK(int_get, "int get()");
    CHECK(long_get, "long get()");
}

出力

1つはtypedefタイプです
2 つは typedef 型を持っています
typedef 型を持たない
int には typedef 型がありません

1 つは long に変換可能な typedef 型を持っています
2 つには long に変換可能な typedef 型があります
long に変換可能な typedef 型がありません
int には long に変換可能な typedef 型がありません

1つは typedef type = int です
2 つは typedef type = int を持っています
typedef type = int を持たない
int には typedef type = int がありません

typedef type = long を持たないもの
2つはtypedef type = longを持っていません
typedef type = long を持たない
int には typedef type = long がありません
-------------------------------------------------- ----------
1つはvar xを持っています
2 には var x があります
var x を持たない
int には var x がありません

1 つは long に変換可能な型の var x を持ちます
2 には long に変換可能な型の var x があります
long に変換可能な型の var x がありません
int には long に変換可能な型の var x がありません

1 つは int 型の var x を持ちます
2 には int 型の var x があります
int 型の var x を持たない
int には int 型の var x がありません

long 型の var x がありません
Two には long 型の var x がありません
long 型の var x がありません
int には long 型の var x がありません
-------------------------------------------------- ----------
値が true の var v がある
Two には真に等しい値を持つ var v があります
値が true の var v を持たない
int には、値が true に等しい var v がありません

値が true の var z がない
Two には、値が true の var z がありません
値が true の var z を持たない
int には、値が true の var z がありません

値が false の var v がない
Two には、値が false の var v がありません
値が false の var v を持たない
int には、値が false の var v がありません

値が 1 の var v を持つもの
Two には、値が 1 に等しい var v があります
値が 1 の var v を持たない
int には、値が 1 に等しい var v がありません

int 型の 1 に等しい値を持つ var v がない
Two には、int 型の 1 に等しい値を持つ var v がありません
int 型の 1 に等しい値を持つ var v を持たない
int には、int 型の 1 に等しい値を持つ var v がありません
-------------------------------------------------- ----------
get() がある
2 つが get() を持っています
get() がありません
int には get() がありません

戻り値の型を long に変換できる get() がある
2 つには get() があり、戻り値の型を long に変換できます
戻り値の型を long に変換できる get() がありません
int には、戻り値の型を long に変換できる get() がありません

1 つは、2 つの int を受け入れて ~ long を返す add() です。
Two には、2 つの int を受け入れて ~ long を返す add() があります。
2 つの int を受け入れて ~ long を返す add() がありません
int には、2 つの int を受け入れて ~ long を返す add() がありません

1 つは int get() を持っています
2 つは int get() を持っています
int get() を持たない
int には int get() がありません

長い get() がありません
2 つには長い get() がありません
長い get() がありません
int には長い get() がありません
于 2014-08-22T13:18:39.193 に答える