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的数据或函数成员,具有任意类型。引入成员名称的唯一目的是使成员名称查找可能存在歧义 - 成员的类型并不重要。
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 == true
和HasX<B>::value == false
。让我们看看为什么。
首先回想一下,std::false_type
并且std::true_type
有一个static constexpr bool
名为的成员,分别value
设置为false
和true
。因此,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
默认值int
,Has<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
。
感谢默认的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);
哪些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
也有类型bool
和decltype(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) f
铸f
从foo
到void
基本上什么也不做,但说的表达式必须被视为具有类型void
。然后使用内置运算符逗号并生成具有类型的((void) f, 0)
结果。因此,意味着。0
int
decltype((void) f, 0)
int
这个演员真的有必要吗?好吧,如果逗号运算符没有超载foo
,int
那么这是没有必要的。我们总是可以检查源代码,看看是否有这样的运算符。但是,如果它出现在模板中且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 ++,因此T
用bool
in 替换会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
)中的任何地方使用。然后我们可以取消命名第二个模板参数,并获得本文顶部显示的代码。
- 3 回答
- 0 关注
- 789 浏览
添加回答
举报