我想知道一个类里有没有定义 value_type,但是为什么 has_value_type_1 可以做到,has_value_type_2 做不到。查了半天了,还是没搞明白。
// c++98 version
#include <iostream>
struct false_type { const static bool value=false; };
struct true_type { const static bool value=true; };
template <typename> struct type_sink { typedef void type; };
template <typename, typename = void> struct has_value_type_1 : false_type {};
template <typename, typename = void> struct has_value_type_2 : false_type {};
template <typename T> struct has_value_type_1< T, typename type_sink< typename T::value_type >::type > : true_type {};
template <typename T> struct has_value_type_2< T, typename T::value_type > : true_type {};
struct A { typedef int value_type; };
struct B { };
template <typename T>
bool f_1(T) { return has_value_type_1<T>::value; }
template <typename T>
bool f_2(T) { return has_value_type_2<T>::value; }
#include <iostream>
int main()
{
A a;
B b;
bool x[4] = {f_1(a), f_1(b), f_2(a), f_2(b)};
for(int i=0;i<4;i++)
std::cout<<x[i]<<' ';
return 0;
}
1
yorTX9t 2017-09-03 22:22:51 +08:00
你用的是什么编译器? VC 在 two phase name look up 的实现上有 bug,你用的是这个么?还没看代码,只是猜测。
|
2
gnaggnoyil 2017-09-03 22:45:13 +08:00
has_value_type2 的偏特化错了.has_value_type<A, void>并不能选取 has_value_type<A, int>作为自己的特化实例.
|
3
scinart OP @yorTX9t 我 clang 4.0 和 gcc 7.1
@gnaggnoyil 我想偏特化,但是编译器没给我偏特化,所以是不是编译器应该给我报个错?但是我这没错没警告,那 clang 和 gcc 背后干了啥? |
4
yorTX9t 2017-09-03 23:39:25 +08:00
```
//template <typename, typename = void> struct has_value_type_2 : false_type {}; template <typename, typename = int> struct has_value_type_2 : false_type {}; ``` |
5
gnaggnoyil 2017-09-03 23:47:35 +08:00 1
@scinart 因为你 has_value_type2 的写法就弄出来了一个不偏特化也是 well-formed 的上下文啊……具体原因我 2L 已经说了,has_value_type2<A>因为是 has_value_type2<A, void>,而 void != typename A::value_type,所以是用的主模板版本进行的实例化.
|
6
scinart OP @gnaggnoyil 我能理解它用主模板版本进行的实例化,我不理解的是,当编译器看到 has_value_type_2< A, int> 的时候,它是怎么做的。
我的理解是:has_value_type_2 需要两个模板参数,第二个不写则默认为 void template <typename T> struct has_value_type_2 是一个 partial specialization,两个模板参数分别是 T, typename T::value_type 那么,从模板匹配上说,has_value_type<A, int>成功匹配上了 partial specialization 的模板,为什么还要使用主模板呢? |
7
gnaggnoyil 2017-09-04 00:12:40 +08:00
@scinart 因为试图对偏特化模板进行匹配的实例不是 has_value_type2<A, int>,而是 has_value_type2<A, void>……你注意到主模板第二个参数的默认参数是什么了吗……
|
8
yorTX9t 2017-09-04 00:12:55 +08:00
@gnaggnoyil 我来说下我的理解吧。不一定正确,只供参考。
1. 在 template definition phase,也就是 two-phase name lookup 的 phase 1,编译器在处理 has_value_type_1<T>::value 和 has_value_type_2<T>::value 的时候,编译器寻找 non-dependent 的模板匹配,这时候 has_value_type_1<T> 和 has_value_type_2<T> 都被理解为从 false_type 里边继承的,因为 template <typename, typename = void> struct has_value_type_1 : false_type {} 不需要依赖任何模板参数。于是函数 template< typename T> bool f_1(T) { return has_value_type_1<T>::value; } 被理解为 template<typename T> bool f_1(T){ return has_value_type_1<T,void>::value; }; ; f_2(T) 也是如此,被理解为 has_value_type_2<T,void>::value;,因为编译器不能得知模板参数 T 的内部,因此后边的两个偏特化 template <typename T> struct has_value_type_1< T, typename type_sink< typename T::value_type >::type > : true_type {}; 和 template <typename T> struct has_value_type_2< T, typename T::value_type > : true_type {}; 都不会被处理。 2. 在 template instantiation phase,也就是 phase 2,编译器为 has_value_type_1<T,void> 寻找合适的匹配,这时候编译器看到了两个偏特化,一个写出来是 template< typename T = A > struct has_value_type_1<A,void>:true_type;,另一个写出来是 template< typename T = A > struct has_value_type_2<A,int>:true_type;。于是头一个被选中,替代 bool f_1(A) 中的那个 has_value_type_1<A,void>,于是为 true_type,而另外一个不变,还是 false type。 如果将主楼代码中的 template <typename> struct type_sink { typedef void type; }; 替换为 template <typename> struct type_sink { typedef int type; }; 那么所有的输出当为 false,因为偏特化时 has_value_type_1 也未被选中。 |
10
yangff 2017-09-04 01:22:29 +08:00 1
因为你用模板的姿势有点偏差…… 首先你可能误解了模板的特化…… 模板的特化实际上是模式匹配,而不是某种自动填充……
你可以试试 template <class T> struct X {}; template<> struct X<int> {};在做的是当 X=int 的时候选择后一条路径做特化,而不是当我不填 T 的时候选择 T=int 来编译后面那个特化……你的 has_value_type_2 很明显是在干这件事…… 如果你想这么干,请用继承或者 typedef 之类的…… 看你的要求 其次你可能误解了模板的默认值 模板的默认值就是默认值,只要你不填这个参数他就永远是默认值…… 然后你这里的问题是…… 你之所以能写 has_value_type_<T>实际上是因为你的 typename=void, 这个默认值和你怎么特化无关,也就是说也就是去掉这个=void,把你的模板参数完整写出来,你在写的其实一直都是 has_value_type_1<T, void> has_value_type_2<T, void>,你完全可以搞个 struct has_value_type_XX : has_value_type_1<T, void>来代替这个 typename=void,这样会显得更清晰一些…… 然后我们来看你的 has_value_type_1 template <typename T> struct has_value_type_1< T, typename type_sink< typename T::value_type >::type > : true_type {}; 不难注意到,如果 T 有 value_type,无论他的类型是什么,你始终通过 type_sink 对 has_value_type_1<T, void>做特化,使得它为 true 反之,如果 T 没有 value_type,由于 SFINAE,就不会有这条特化,从而去匹配 primary 也就是 struct has_value_type_1<T, typename = void> : false_type 这条路径,于是就可以得到正确结果 然后我们来看你的第二个写法,struct has_value_type_2<T, typename T::type_value>,那么 T::type_value 是啥? 是 int,也就是说你特化了 has_value_type_2<T, int> : true_value ;而你调用的是啥? has_value_type_2<T, void>,于是这里并不匹配,c++用的还是 has_value_type_2 : false_value 这条。从而你取到的还是 false,除非你显示使用 has_value_type_2<T, int>或者把 A 的 int 改成 void …… 这两者明显是不大靠谱的…… |
11
scinart OP |
12
linux40 2017-09-04 07:51:56 +08:00 via Android
原来很久以前我看 glibc++里有个 void_t,原来是真么回事。。。
|