这是一个比以下更简单的解决方案约翰内斯·绍布 (Johannes Schaub) - 英文 https://stackoverflow.com/users/34509/johannes-schaub-litb's one https://stackoverflow.com/a/1007175/1137388。它需要 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 { };
Update:一个简单的示例以及其工作原理的解释。
对于这些类型:
struct A { int x; };
struct B { int y; };
we have HasX<A>::value == true
and HasX<B>::value == false
。让我们看看为什么。
首先回想一下std::false_type
and std::true_type
have a static constexpr bool
成员名为value
设置为false
and 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
and HasX<bool, int>::value == false
.
2) 违约U
:
// Primary template
template <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 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 { };
一般来说,由于主要模板,HasX<T, U>
源自于std::false_type
。然而,有一个专门针对U = int
其源自std::true_type
。所以,HasX<bool, double>::value == false
but HasX<bool, int>::value == true
.
感谢默认的U
, HasX<bool>::value == HasX<bool, int>::value == true
.
4) decltype
和一种奇特的说法int
:
这里有点离题,但是请耐心听我说。
基本上(这并不完全正确),decltype(expression)
产生的类型表达。例如,0
有类型int
thus, decltype(0)
means int
。类似地,1.2
有类型double
因此,decltype(1.2)
means double
.
考虑一个带有此声明的函数:
char func(foo, int);
where foo
是某种类类型。如果f
是一个类型的对象foo
, then decltype(func(f, 0))
means char
(返回的类型func(f, 0)
).
现在,表达(1.2, 0)
使用(内置)逗号运算符按顺序计算两个子表达式(即,首先1.2
进而0
),丢弃第一个值并产生第二个值。因此,
int x = (1.2, 0);
相当于
int x = 0;
把这个和decltype
给出了decltype(1.2, 0)
means int
。没什么特别的1.2
or double
这里。例如,true
有类型bool
and decltype(true, 0)
means int
以及。
班级类型怎么样?例如,什么是decltype(f, 0)
意思是?很自然地期望这仍然意味着int
但情况可能并非如此。事实上,逗号运算符可能存在类似于函数的重载func
上面需要一个foo
and an int
并返回一个char
。在这种情况下,decltype(foo, 0)
is char
.
我们如何避免使用逗号运算符的重载?好吧,没有办法重载 a 的逗号运算符void
操作数,我们可以将任何内容转换为void
。所以,decltype((void) f, 0)
means int
。的确,(void) f
casts f
from foo
to void
它基本上什么也没做,只是说表达式必须被视为具有类型void
。然后使用内置运算符逗号((void) f, 0)
结果是0
其中有类型int
。因此,decltype((void) f, 0)
means int
.
这个演员阵容真的有必要吗?好吧,如果逗号运算符没有重载foo
and 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 { };
又是在这里,decltype((void) T::x, 0)
是一种奇特的表达方式int
但得益于 SFINAE。
When T
替换为类型时,可能会出现无效的构造。例如,bool::x
不是有效的 C++,因此替换T
with bool
in T::x
产生无效的构造。根据 SFINAE 原则,编译器不会拒绝代码,它只是忽略(部分)它。更准确地说,正如我们所看到的HasX<bool>
实际上意味着HasX<bool, int>
。专业化为U = int
应该选择,但是在实例化它时,编译器发现bool::x
并完全忽略模板专业化,就好像它不存在一样。
此时,代码本质上与上面情况 (2) 中的代码相同,其中仅存在主模板。因此,HasX<bool, int>::value == false
.
同样的论点用于bool
成立于B
since B::x
是一个无效的构造(B
没有会员x
)。然而,A::x
没问题,编译器在实例化专门化时没有发现任何问题U = int
(或者,更准确地说,对于U = decltype((void) A::x, 0)
)。因此,HasX<A>::value == true
.
6) 取消命名U
:
好吧,再次查看(5)中的代码,我们看到名称U
除了在其声明中之外没有在任何地方使用(typename U
)。然后我们可以取消第二个模板参数的命名,并获得本文顶部显示的代码。