为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

C++
慕少森 2019-07-31 18:06:43
如何检测类中是否有特定的成员变量?为了创建算法模板函数,我需要知道类中的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;}但是在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; }但它不能在GNU C ++中编译。有通用解决方案吗?UPD:此处的结构P1和P2仅作为示例。可能有任何具有未知成员的类。PS请不要在这里发布C ++ 11解决方案,因为它们很明显且与问题无关。
查看完整描述

3 回答

?
波斯汪

TA贡献1811条经验 获得超4个赞

另一种方式是这种方式,它也依赖于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}

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

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


查看完整回答
反对 回复 2019-07-31
?
慕桂英4014372

TA贡献1871条经验 获得超13个赞

这里是一个解决方案不是简单的 约翰内斯·绍布- 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 { };

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

对于这些类型:

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

我们有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 { };

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

2)违约U

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

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

3)添加专业化:

// Primary templatetemplate <typename T, typename U = int>struct HasX : std::false_type { };// Specialization for U = inttemplate <typename T>struct HasX<T, int> : std::true_type { };

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

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

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

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

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

考虑具有此声明的函数:

char func(foo, int);

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

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

int x = (1.2, 0);

相当于

int x = 0;

一起把这个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

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

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

5)SFINAE:

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

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

// Primary templatetemplate <typename T, typename U = int>struct HasX : std::false_type { };// Specialization for U = inttemplate <typename T>struct HasX <T, decltype((void) T::x, 0)> : std::true_type { };

在这里,再次说,这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)中的任何地方使用。然后我们可以取消命名第二个模板参数,并获得本文顶部显示的代码。


查看完整回答
反对 回复 2019-07-31
  • 3 回答
  • 0 关注
  • 789 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信