如何检测类中是否有特定的成员变量?

Kir*_*sky 62 c++ templates g++ sfinae visual-studio

为了创建算法模板函数,我需要知道类中的x或X(和y或Y)是模板参数.当我的函数用于MFC CPoint类或GDI + PointF类或其他类时,它可能很有用.他们都使用不同的x.我的解决方案可以简化为以下代码:


template<int> struct TT {typedef int type;};
template<class P> bool Check_x(P p, typename TT<sizeof(&P::x)>::type b = 0) { return true; }
template<class P> bool Check_x(P p, typename TT<sizeof(&P::X)>::type b = 0) { return false; }

struct P1 {int x; };
struct P2 {float X; };
// it also could be struct P3 {unknown_type X; };

int main()
{
    P1 p1 = {1};
    P2 p2 = {1};

    Check_x(p1); // must return true
    Check_x(p2); // must return false

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

但是在GNU C++中进行编译时,它无法在Visual Studio中编译.使用Visual Studio,我可以使用以下模板:


template<class P> bool Check_x(P p, typename TT<&P::x==&P::x>::type b = 0) { return true; }
template<class P> bool Check_x(P p, typename TT<&P::X==&P::X>::type b = 0) { return false; }
Run Code Online (Sandbox Code Playgroud)

但它不能在GNU C++中编译.有通用解决方案吗?

UPD:此处的结构P1和P2仅作为示例.可能有任何具有未知成员的类.

PS请不要在这里发布C++ 11解决方案,因为它们很明显且与问题无关.

Cas*_*eri 83

这里是一个解决方案不是简单的 约翰内斯·绍布- litb一个.它需要C++ 11.

#include <type_traits>

template <typename T, typename = int>
struct HasX : std::false_type { };

template <typename T>
struct HasX <T, decltype((void) T::x, 0)> : std::true_type { };
Run Code Online (Sandbox Code Playgroud)

更新:一个简单的例子和​​解释如何工作.

对于这些类型:

struct A { int x; };
struct B { int y; };
Run Code Online (Sandbox Code Playgroud)

我们有HasX<A>::value == trueHasX<B>::value == false.让我们看看为什么.

首先回想一下,std::false_type并且std::true_type有一个static constexpr bool名为的成员,分别value设置为falsetrue.因此,HasX上面的两个模板继承了这个成员.(来自std::false_type的第一个模板和来自的第二个模板std::true_type.)

让我们开始简单,然后一步一步地进行,直到我们得到上面的代码.

1)起点:

template <typename T, typename U>
struct HasX : std::false_type { };
Run Code Online (Sandbox Code Playgroud)

在这种情况下,毫不奇怪:HasX派生于std::false_type和因此HasX<bool, double>::value == false而且HasX<bool, int>::value == false.

2)违约U:

// Primary template
template <typename T, typename U = int>
struct HasX : std::false_type { };
Run Code Online (Sandbox Code Playgroud)

鉴于U默认值int,Has<bool>实际上意味着HasX<bool, int>,因此,HasX<bool>::value == HasX<bool, int>::value == false.

3)添加专业化:

// Primary template
template <typename T, typename U = int>
struct HasX : std::false_type { };

// Specialization for U = int
template <typename T>
struct HasX<T, int> : std::true_type { };
Run Code Online (Sandbox Code Playgroud)

一般来说,感谢主要模板,HasX<T, U>源于std::false_type.但是,存在一种U = int衍生自的专业化std::true_type.因此,HasX<bool, double>::value == false但是HasX<bool, int>::value == true.

感谢默认的U,HasX<bool>::value == HasX<bool, int>::value == true.

4)decltype和一种奇特的说法int:

这里有点偏离,但是,拜托,请耐心等待.

基本上(这不完全正确),decltype(expression)产生 表达式.例如,因此0具有类型int,decltype(0)意味着int.类似地,1.2具有类型double,因此,decltype(1.2)意味着double.

考虑具有此声明的函数:

char func(foo, int);
Run Code Online (Sandbox Code Playgroud)

哪些foo是类类型.如果f是类型的对象foo,则decltype(func(f, 0))表示char(返回的类型func(f, 0)).

现在,表达式(1.2, 0)使用(内置)逗号运算符按顺序计算两个子表达式(即首先1.2和然后0),丢弃第一个值并产生第二个值.因此,

int x = (1.2, 0);
Run Code Online (Sandbox Code Playgroud)

相当于

int x = 0;
Run Code Online (Sandbox Code Playgroud)

一起把这个decltype给出decltype(1.2, 0)的手段int.有没有什么特别的地方1.2或者double在这里.例如,true也有类型booldecltype(true, 0)手段int.

班级类型怎么样?对于instace,decltype(f, 0)意味着什么?人们很自然地认为这仍然意味着int可能并非如此.实际上,逗号运算符可能有一个重载类似于func上面的函数,它接受a foo和a int并返回a char.在这种情况下,decltype(foo, 0)char.

我们如何避免使用逗号运算符的重载?好吧,没有办法为void操作数重载逗号运算符,我们可以将任何内容转换为void.因此,decltype((void) f, 0)意味着int.实际上,(void) fffoovoid基本上什么也不做,但说的表达式必须被视为具有类型void.然后使用内置运算符逗号并生成具有类型的((void) f, 0)结果.因此,意味着.0intdecltype((void) f, 0)int

这个演员真的有必要吗?好吧,如果逗号运算符没有超载foo,int那么这是没有必要的.我们总是可以检查源代码,看看是否有这样的运算符.但是,如果它出现在模板中且f类型V为模板参数,则不再清楚(甚至不可能知道)逗号运算符的这种重载是否存在.无论如何我们都是通用的.

底线:decltype((void) f, 0)是一种奇特的说法int.

5)SFINAE:

这是一门完整的科学;-)好吧,我正在劝告,但这也不是很简单.所以我会把解释保持在最低限度.

SFINAE代表替换失败并非错误.这意味着当一个模板参数被一个类型替换时,可能会出现一个非法的C++代码,但是在某些情况下,编译器只是忽略了有问题的代码,就好像它不存在一样.让我们看看它如何适用于我们的案例:

// Primary template
template <typename T, typename U = int>
struct HasX : std::false_type { };

// Specialization for U = int
template <typename T>
struct HasX <T, decltype((void) T::x, 0)> : std::true_type { };
Run Code Online (Sandbox Code Playgroud)

在这里,再次说,这decltype((void) T::x, 0)是一种奇特的方式,int但有SFINAE的好处.

T用类型替换时,可能会出现无效的构造.例如,bool::x无效的C++,因此Tboolin 替换会T::x产生无效的构造.根据SFINAE原则,编译器不会拒绝代码,它只是忽略它的(部分).更确切地说,正如我们所看到HasX<bool>的实际含义HasX<bool, int>.对于专业化U = int应进行选择,但同时将其实例化,编译器发现bool::x并完全忽略了模板专业化,就好像它不存在.

此时,代码基本上与上面仅存在主模板的情况(2)相同.因此,HasX<bool, int>::value == false.

用于相同的论点bool适用于B因为B::x是一个无效的构建体(B没有成员x).但是,A::x没关系,编译器在实例化U = int(或者更确切地说是for U = decltype((void) A::x, 0))的特化时没有看到任何问题.因此,HasX<A>::value == true.

6)取消U:

好吧,再次查看(5)中的代码,我们看到该名称U不会在其声明(typename U)中的任何地方使用.然后我们可以取消命名第二个模板参数,并获得本文顶部显示的代码.

  • @DarioP:我添加了一个带有解释的更新.正如您所看到的那样,解释非常冗长,因为正确的细节数量总是取决于读者的知识.为了初学者的利益,我假设很少.我希望这很清楚但不乏味. (3认同)
  • 这是我见过的最干净、最简单的实现。对我来说,GCC 的问题是通过使用默认模板参数 `void` 而不是 `int` 来解决的,然后删除 `decltype` 中的逗号操作符 - http://ideone.com/QZHfai (2认同)

Joh*_*itb 48

另一种方式是这种方式,它也依赖于SFINAE表达式.如果名称查找导致歧义,编译器将拒绝该模板

template<typename T> struct HasX { 
    struct Fallback { int x; }; // introduce member name "x"
    struct Derived : T, Fallback { };

    template<typename C, C> struct ChT; 

    template<typename C> static char (&f(ChT<int Fallback::*, &C::x>*))[1]; 
    template<typename C> static char (&f(...))[2]; 

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

struct A { int x; };
struct B { int X; };

int main() { 
    std::cout << HasX<A>::value << std::endl; // 1
    std::cout << HasX<B>::value << std::endl; // 0
}
Run Code Online (Sandbox Code Playgroud)

它基于usenet上有人的精彩想法.

注意:HasX检查任何名为x的数据或函数成员,具有任意类型.引入成员名称的唯一目的是使成员名称查找可能存在歧义 - 成员的类型并不重要.

  • 是啊,屏住呼吸:http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1(从SO线程您可以通过一个链接太多链接的家伙).不用担心,如果你没有立即得到那个人的代码.这很聪明,我也花了很长时间 (3认同)
  • 现在这个代码的完整解释已经发布了..:http://cpptalk.wordpress.com/2009/09/12/substitution-failure-is-not-an-error-2/ litb,非常欢迎您验证我得到了正确的:) (2认同)

And*_*owl 30

我从一个已被关闭的问题重定向到这里,作为这个问题的副本.我知道这是一个旧线程,但我只是想建议一个与C++ 11一起使用的替代(更简单?)实现.假设我们要检查某个类是否有一个名为的成员变量id:

#include <type_traits>

template<typename T, typename = void>
struct has_id : std::false_type { };

template<typename T>
struct has_id<T, decltype(std::declval<T>().id, void())> : std::true_type { };
Run Code Online (Sandbox Code Playgroud)

而已.以下是它的使用方法(实例):

#include <iostream>

using namespace std;

struct X { int id; };
struct Y { int foo; };

int main()
{
    cout << boolalpha;
    cout << has_id<X>::value << endl;
    cout << has_id<Y>::value << endl;
}
Run Code Online (Sandbox Code Playgroud)

使用几个宏可以使事情变得更简单:

#define DEFINE_MEMBER_CHECKER(member) \
    template<typename T, typename V = bool> \
    struct has_ ## member : false_type { }; \
    template<typename T> \
    struct has_ ## member<T, \
        typename enable_if< \
            !is_same<decltype(declval<T>().member), void>::value, \
            bool \
            >::type \
        > : true_type { };

#define HAS_MEMBER(C, member) \
    has_ ## member<C>::value
Run Code Online (Sandbox Code Playgroud)

可以这样使用:

using namespace std;

struct X { int id; };
struct Y { int foo; };

DEFINE_MEMBER_CHECKER(foo)

int main()
{
    cout << boolalpha;
    cout << HAS_MEMBER(X, foo) << endl;
    cout << HAS_MEMBER(Y, foo) << endl;
}
Run Code Online (Sandbox Code Playgroud)

  • 我认为你根本不需要 `declval`,在我的测试中 `decltype(T::id, void())` 工作得很好 (2认同)

Sha*_*our 13

我们可以使用C++20的require表达式来解决这个问题。h/t 致@lefticus,他最近在C++ Weekly - Ep 242 - Design By Introspection in C++20 中发布了此方法(概念 + if constexpr

#include <iostream>

struct P1 {int x;};
struct P2 {float X;};

bool has_x(const auto &obj) {
    if constexpr (requires {obj.x;}) {
      return true;
    } else
      return false;
}

int main()
{
    P1 p1 = {1};
    P2 p2 = {1};

    std::cout << std::boolalpha << has_x(p1) << "\n"; 
    std::cout << has_x(p2) << "\n"; 

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

你可以在这里看到它的现场直播。


Bre*_*ier 7

更新:我最近使用我在原始答案中发布的代码完成了更多工作,因此我将更新此代码以考虑更改/添加内容.

以下是一些使用片段:*所有这些的胆量更远

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

CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;
Run Code Online (Sandbox Code Playgroud)

检查会员功能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;
Run Code Online (Sandbox Code Playgroud)

检查成员变量x:

CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;
Run Code Online (Sandbox Code Playgroud)

检查会员类x:

CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;
Run Code Online (Sandbox Code Playgroud)

检查成员联盟x:

CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;
Run Code Online (Sandbox Code Playgroud)

检查成员枚举x:

CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;
Run Code Online (Sandbox Code Playgroud)

检查任何成员函数,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;
Run Code Online (Sandbox Code Playgroud)

要么

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;
Run Code Online (Sandbox Code Playgroud)

细节和核心:

/*
    - 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.
*/

template <typename... Args> struct ambiguate : public Args... {};

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;
};
Run Code Online (Sandbox Code Playgroud)

宏(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                                                            \
    ;                                                                       \
}
Run Code Online (Sandbox Code Playgroud)

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 {}
Run Code Online (Sandbox Code Playgroud)

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 {}
Run Code Online (Sandbox Code Playgroud)

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 {}
Run Code Online (Sandbox Code Playgroud)

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 {}
Run Code Online (Sandbox Code Playgroud)

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 {}
Run Code Online (Sandbox Code Playgroud)

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    \
    ;                                           \
}
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)