求助,问一个 c++模板推导的问题。 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
scinart
V2EX    C

求助,问一个 c++模板推导的问题。

  •  
  •   scinart 2017-09-03 22:19:43 +08:00 3024 次点击
    这是一个创建于 2973 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我想知道一个类里有没有定义 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 tpe_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; } 
    12 条回复    2017-09-04 07:51:56 +08:00
    yorTX9t
        1
    yorTX9t  
       2017-09-03 22:22:51 +08:00
    你用的是什么编译器? VC 在 two phase name look up 的实现上有 bug,你用的是这个么?还没看代码,只是猜测。
    gnaggnoyil
        2
    gnaggnoyil  
       2017-09-03 22:45:13 +08:00
    has_value_type2 的偏特化错了.has_value_type<A, void>并不能选取 has_value_type<A, int>作为自己的特化实例.
    scinart
        3
    scinart  
    OP
       2017-09-03 23:02:28 +08:00
    @yorTX9t 我 clang 4.0 和 gcc 7.1

    @gnaggnoyil 我想偏特化,但是编译器没给我偏特化,所以是不是编译器应该给我报个错?但是我这没错没警告,那 clang 和 gcc 背后干了啥?
    yorTX9t
        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 {};
    ```
    gnaggnoyil
        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,所以是用的主模板版本进行的实例化.
    scinart
        6
    scinart  
    OP
       2017-09-04 00:02:36 +08:00
    @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 的模板,为什么还要使用主模板呢?
    gnaggnoyil
        7
    gnaggnoyil  
       2017-09-04 00:12:40 +08:00
    @scinart 因为试图对偏特化模板进行匹配的实例不是 has_value_type2<A, int>,而是 has_value_type2<A, void>……你注意到主模板第二个参数的默认参数是什么了吗……
    yorTX9t
        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 也未被选中。
    yorTX9t
        9
    yorTX9t  
       2017-09-04 00:54:55 +08:00
    @scinart 啊,上边的回复 at 错了人
    yangff
        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 …… 这两者明显是不大靠谱的……
    scinart
        11
    scinart  
    OP
       2017-09-04 02:42:21 +08:00
    @yangff @yorTX9t 感谢回复,这下懂了。

    然后再次感谢一下 @yangff 解释的太清楚了。

    总结一下我的理解误差:我以为 template <typename T> struct has_value_type 是定义了一个只接受一个模板参数的特殊的 has_value_type,当编译器运到 has_value_type 只有一个模板参数时,会优先选择这个定义:

    事实是:如上写法中 has_value_type 始终接受两个参数,第二个是 void,编译器找到主模板后再用模式匹配找 specialization,有则用之。
    linux40
        12
    linux40  
       2017-09-04 07:51:56 +08:00 via Android
    原来很久以前我看 glibc++里有个 void_t,原来是真么回事。。。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2472 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 36ms UTC 05:21 PVG 13:21 LAX 22:21 JFK 01:21
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86