关于c ++:SFINAE来检查继承的成员函数

SFINAE to check for inherited member functions

使用sfinae,我可以检测给定类是否具有某个成员函数。但是如果我想测试继承的成员函数呢?

以下内容在VC8和GCC4中不起作用(即检测到A具有成员函数foo()而不是B继承一个成员函数):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#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
}

那么,有没有一种方法来测试继承的成员函数呢?


看看这条线:

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

源于讨论中链接到的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#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::result << endl;
    cout << boolalpha << has_foo<C>::result;
}

结果:

1
2
3
true
true
false


乔什佩里的答案非常聪明和优雅,但是(如文章下面所述)它没有正确地检查foo()的签名,并且不适用于基本类型(如int):它会导致编译器错误。我将提出一种正确处理继承成员并检查成员函数签名的技术。我将给你两个例子,希望代码能说明问题,而不是详细讨论细节。

实例1:

我们正在检查是否有以下签名的成员:T::const_iterator begin() const

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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));
};

请注意,它甚至会检查方法的结构,并与基元类型一起使用。(我的意思是has_const_begin::value是错误的,不会导致编译时错误。)

例2

现在我们在找签名:void foo(MyClass&, unsigned)

1
2
3
4
5
6
7
8
9
10
11
12
13
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不必是默认可构造的,也不必满足任何特殊的概念。该技术也适用于模板成员。

我热切地等待关于这一点的意见。


以下是一些用法片段:*所有这些的胆量都在进一步下降。

检查给定类中的成员x。可以是var、func、class、union或enum:

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

检查成员函数void x()

1
2
3
//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

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

检查成员类x

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

检查是否有会员工会x

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

检查成员枚举x

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

检查是否有任何成员函数x,不考虑签名:

1
2
3
4
5
6
7
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;

1
2
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;

细节和核心:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/*
    - 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!):

创建成员检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//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>            \
            , Alias_##member<AmbiguitySeed_##member>                        \
        >::value                                                            \
    ;                                                                       \
}

创建成员变量检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//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 {}

创建成员检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
//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 {}

创建u成员u类u检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//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 {}

创建成员联合检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//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 {}

创建成员枚举检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//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 {}

创建成员检查:

1
2
3
4
5
6
7
8
9
10
11
12
//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    \
    ;                                           \
}

创建成员检查:

1
2
3
4
5
6
7
8
//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)


由于所有的答案对我来说都太复杂了,我想介绍一下我自己使用std::declvalstd::enable_if的解决方案(GCC4.8.3)。

1
2
3
4
5
#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)

试验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
2
3
4
5
6
7
One has int get()
Two has int get()
Not does not have int get()
One has int add(int, int)
Two has int add(int, int)
Not does not have int add(int, int)
int does not have int get()

更新:我的支票

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/// 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 {};

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
One has typedef type
Two has typedef type
Not does not have typedef type
int does not have typedef type

One has typedef type convertible to long
Two has typedef type convertible to long
Not does not have typedef type convertible to long
int does not have typedef type convertible to long

One has typedef type = int
Two has typedef type = int
Not does not have typedef type = int
int does not have typedef type = int

One does not have typedef type = long
Two does not have typedef type = long
Not does not have typedef type = long
int does not have typedef type = long
------------------------------------------------------------
One has var x
Two has var x
Not does not have var x
int does not have var x

One has var x of type convertible to long
Two has var x of type convertible to long
Not does not have var x of type convertible to long
int does not have var x of type convertible to long

One has var x of type int
Two has var x of type int
Not does not have var x of type int
int does not have var x of type int

One does not have var x of type long
Two does not have var x of type long
Not does not have var x of type long
int does not have var x of type long
------------------------------------------------------------
One has var v with value equal to true
Two has var v with value equal to true
Not does not have var v with value equal to true
int does not have var v with value equal to true

One does not have var z with value equal to true
Two does not have var z with value equal to true
Not does not have var z with value equal to true
int does not have var z with value equal to true

One does not have var v with value equal to false
Two does not have var v with value equal to false
Not does not have var v with value equal to false
int does not have var v with value equal to false

One has var v with value equal to 1
Two has var v with value equal to 1
Not does not have var v with value equal to 1
int does not have var v with value equal to 1

One does not have var v with value equal to 1 of type int
Two does not have var v with value equal to 1 of type int
Not does not have var v with value equal to 1 of type int
int does not have var v with value equal to 1 of type int
------------------------------------------------------------
One has get()
Two has get()
Not does not have get()
int does not have get()

One has get() with return type covertible to long
Two has get() with return type covertible to long
Not does not have get() with return type covertible to long
int does not have get() with return type covertible to long

One has add() accepting two ints and returning ~ long
Two has add() accepting two ints and returning ~ long
Not does not have add() accepting two ints and returning ~ long
int does not have add() accepting two ints and returning ~ long

One has int get()
Two has int get()
Not does not have int get()
int does not have int get()

One does not have long get()
Two does not have long get()
Not does not have long get()
int does not have long get()