这里是一个解决方案不是简单的
约翰内斯·绍布- 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 。因此,意味着ffoovoidvoid((void) f, 0)0intdecltype((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用boolin替换将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)中使用。然后,我们可以取消命名第二个模板参数,并获得本文顶部显示的代码。