这里是一个解决方案不是简单的
约翰内斯·绍布- 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
:
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)添加专业化:
template <typename T, typename U = int>
struct HasX : std::false_type { };
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
但是HasX<bool, int>::value == true
。
多亏了默认设置U
,HasX<bool>::value == HasX<bool, int>::value == true
。
4) decltype
和一种奇特的说法int
:
这里有点题外话,但是请忍受我。
基本上(这并不完全正确)会decltype(expression)
产生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
上面的函数类似,需要afoo
和anint
并返回a char
。在这种情况下,decltype(foo, 0)
是char
。
我们如何避免对逗号运算符使用重载?好吧,没有办法为void
操作数重载逗号运算符,我们可以将任何内容强制转换为void
。因此,decltype((void) f, 0)
意味着int
。确实,从(void) f
强制转换到基本上什么都没做,只是说必须将表达式视为具有type 。然后使用内置的运算符逗号,结果为type 。因此,意味着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 ++代码,但是在某些情况下,编译器并不会中止有问题的代码,就好像没有出现那样,而不是中止编译。让我们看看它如何适用于我们的案例:
template <typename T, typename U = int>
struct HasX : std::false_type { };
template <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
(或更确切地说是U = decltype((void) A::x, 0)
)的专业化时没有问题。因此,HasX<A>::value == true
。
6)取消命名U
:
好了,再次查看(5)中的代码,我们看到该名称U
未在任何地方使用,而是在其声明(typename U
)中使用。然后,我们可以取消命名第二个模板参数,并获得本文顶部显示的代码。