C++ 类构造时隐式转换的小疑惑 - V2EX
Tony042
V2EX    C++

C++ 类构造时隐式转换的小疑惑

  •  
  •   Tony042 May 24, 2020 3608 views
    This topic created in 2203 days ago, the information mentioned may be changed or developed.

    最近在学 C++的相关知识,在学习类模板参数的辅助自动推导时,有了些疑问,请看下面代码:

    template <typename T, typename COnt= std::vector<T>> class Stack { private: Cont elems; public: Stack() = default; Stack(Stack const &) = default; Stack(T const &elem) : elems({elem}) {} }; Stack(char const *)->Stack<std::string>; // CATD 

    在构造这个类的时候:

    Stack stringStack = "bottom"; // MSVC 16.9 编译通过,GCC-10 编译不通过 Stack stringStack{"bottom"}; // MSVC 16.9 和 GCC-10 均编译通过 

    请问为什么第一个构造方式 GCC 编译不通过呢,是否是因为 MSVC 又启用了某些魔改插件?个人理解是从 Char const[7]或 char const* 转换到 string 算一次转换,然后从 string 到 Stack 进行了二次转换,而 C++只允许进行一次隐式转换,不知是否正确

    Supplement 1    May 24, 2020
    另外还有一个问题,Stack(T const&)这里是引用传递不是值传递所以不应该触发 char const[7]到 char const *的类型衰变( type decay ),但是很奇怪的是这里 GCC 和 MSVC 都编译过去了,没有报错。
    26 replies    2020-05-27 09:18:12 +08:00
    nightwitch
        1
    nightwitch  
       May 24, 2020
    第一个函数你是从"const char [6]" 类型转换到 Stack<std::string>类型, 这个类型系统肯定会报错的, 这也不是构造函数,而是 operator=函数.
    Tony042
        2
    Tony042  
    OP
       May 24, 2020
    @nightwitch operator= 是 copy assignment 函数,我这里就是新构造一个 Stack 类型的 stringStack,没有赋值操作,事实上我在写这个例子时候把 operator=delete 的了,所以这里就是构造函数,之所以不用写类型参数,试用了 C++17 的自动推导,我很疑惑为啥俩编辑器一个通过一个不通过
    Tony042
        3
    Tony042  
    OP
       May 24, 2020
    @nightwitch 不好意思还要再杠一下,是 const char[7]不是 const char[6]因为这个数组最后会自动补一个'\n'
    lechain
        4
    lechain  
       May 24, 2020   1
    很久没接触过 C++了,提个我自己的猜测。

    写法二一定会走 “Stack(T const &elem) : elems({elem}) {}”构造的,这个是显示声明实现了的,所以铁定是没有问题的
    写法一是走的 fault 构造的,二 default 构造你并没有显示实现,所以这个构造得依赖于编译器,编译器以不一样的方式隐式的实现,就会产生不一样的结果

    、char*格式的 string 和 C++ class String 自动类型转换也依赖于其默认构造函数,这又是一个依赖于编译器隐式实现的默认构造

    你也说了,写法一存在两种类型转换,但是考虑到这两种转换都是隐式转换,很显然你无法对依赖于编译器实现的隐式转换报有过多的期望。
    damingxing
        5
    damingxing  
       May 24, 2020   1
    不用纠结,直接用第二种方法就行了。。
    Tony042
        6
    Tony042  
    OP
       May 24, 2020
    @lechain 谢谢你,其实还有个问题 Stack(T const&)这里是引用传递不是值传递所以不应该触发 char const[7]到 char const *的类型衰变( type decay ),但是很奇怪的是这里 GCC 和 MSVC 都编译过去了,没有报错
    constexpr
        7
    constexpr  
       May 24, 2020   1
    CTAD 太复杂, 不充分了解 CTAD 是没法很好回答这个问题的- -
    那个我认为应该是 GCC 的一个 BUG, 他在拷贝初始化的时候没有使用用户定义的推导指引.

    你的第二个问题, 推导指引是这样工作的,他选择了一个合适的推导指引(用户定义或者隐式的),然后推导出类型,这里是 std::string, 然后把这个类型带到类里面,那么这里的 T 就是 string 了, 所以 那个构造函数是 Stack(const std::string &elem)... 你可以用 typeid 确认一下确实是这个类型.
    constexpr
        8
    constexpr  
       May 24, 2020   1
    @constexpr 是 CATD...
    constexpr
        9
    constexpr  
       May 24, 2020   1
    @constexpr whatever. 反正是用户定义的推到指引

    C++17 这个功能具恶心,
    std::vector a{1, 1.2}合法,
    std::vector b{1.2, 1}报错
    你想想这是为什么
    lcdtyph
        10
    lcdtyph  
       May 24, 2020 via iPhone
    @constexpr
    是 CTAD
    secondwtq
        11
    secondwtq  
       May 24, 2020   1
    secondwtq
        12
    secondwtq  
       May 24, 2020   1
    另外第一个是 copy initialization
    const char *和 std::string 之间的转换是标准定义的,不是 implementation-defined
    constexpr
        13
    constexpr  
       May 24, 2020   1
    @constexpr 我重新审视了一下,第一个 GCC 或许不是 BUG
    因为是拷贝构造, 所以等号右边得是 Stack 类型, 而他根据用户定义是推到指引, 最终会引发 char const[7]->String->Stack 的转换,这是不允许的.

    PS: 数组最后一位是'\0', 而非'\n'
    msg7086
        14
    msg7086  
       May 24, 2020
    @constexpr
    GCC
    1.cpp:19:23: error: conversion from 'const char [7]' to non-scalar type 'Stack<std::__cxx11::basic_string<char> >' requested

    Clang
    1.cpp:19:9: error: no viable conversion from 'const char [7]' to 'Stack<std::__cxx11::basic_string<char>, std::vector<std::__cxx1::basic_string<char>,
    std::allocator<std::__cxx11::basic_string<char> > > >'

    看上去的确是发生了 const char[] 到 Stack 的直接转换。
    Tony042
        15
    Tony042  
    OP
       May 24, 2020
    @constexpr 谢谢指正,是'\0'
    Tony042
        16
    Tony042  
    OP
       May 24, 2020
    @constexpr #9 能解释下为什么 std::vector a{1, 1.2}合法, std::vector b{1.2, 1}报错么
    Tony042
        17
    Tony042  
    OP
       May 24, 2020   1
    @msg7086 这个地方,应该是把 Stack(T const &elem)改成 Stack(T const elem)就可以编译通过了,但是 gcc 如果加上-std=c++2a 的话用 ref 版的也可以通过
    Tony042
        18
    Tony042  
    OP
       May 24, 2020
    @secondwtq 你好,谢谢提供的资料,我看了下,貌似这个地方不是 user-defined conversion,由于 Stack 这个类是个模板,在 C++17 以前必须写成 Stack<std::string> s("bottom"),显式指明类模板参数,但是 C++17 支持 class template argument deduction,所以在一部分情况下编译器会自动推导类模板参数,但是也会经常推导出错,比如如果没有最后一行类模板推导指引,一般来说编译器会将 T 推导至 char const[7]而不是推向 std::string,这样的话就会带来很多麻烦。
    Tony042
        19
    Tony042  
    OP
       May 24, 2020
    @secondwtq 刚才又想了下,你说的是有道理的,谢谢
    constexpr
        20
    constexpr  
       May 24, 2020   1
    std::vector a{1, 1.2}
    他将采用隐式的类型推导指引(implicitly-generated guide), std::initializer_list<T>这个行不通, 但是
    vector( size_type count, const T& value, const Allocator& alloc = Allocator());
    确是可行的, 这里 T 被推导为 double 类型, 然后把这个类型带进去, 发现 std::initializer_list<double>是更好的选择, 于是调用这个构造函数(即 vector( std::initializer_list<double> init, const Allocator& alloc = Allocator() );).


    当初那个帮他推导的指引所对照的构造函数却没有调用, 所以类似的 std::vector a{10, 1.2} 是一个有 2 个元素的 vector, 而非是有 10 个元素且默认值为 1.2 的 vector
    Tony042
        21
    Tony042  
    OP
       May 24, 2020
    @constexpr 这玩意真的好复杂,不知道这编译器都是咋写出来的
    Wirbelwind
        22
    Wirbelwind  
       May 26, 2020   1
    这个推导指引被视为 函数了
    Wirbelwind
        23
    Wirbelwind  
       May 26, 2020   1
    @Wirbelwind 之前只看了提示报错,测试不充分

    ---
    Stack stringStack{"bottom"};
    这句可以过是因为 Stack(T const &elem),这里定义的第三个构造函数,如果把去掉是编译不过的 提示也是类型转换问题。

    ---
    这里的问题主要是 模板生成时不允许强制转换
    Wirbelwind
        24
    Wirbelwind  
       May 26, 2020   1
    Stack(char const *)->Stack<std::string>
    这里因为你指引了一个模板 所以应该提供一个专用的 构造函数。

    建议不要学模板这么复杂的功能,心智负担太大。
    以上分析不保证正确
    Tony042
        25
    Tony042  
    OP
       May 26, 2020
    @Wirbelwind #23 是这个构造函数在起作用,我疑惑的地方是,传入引用 ( T const& elem)为什么可以发生 type decay 也就是 char const[7] -> char const *,一般只有传入值的时候会 type decay
    Wirbelwind
        26
    Wirbelwind  
       May 27, 2020
    @Tony042 生成模板代码的时候 那里固定成了 std::string,但是 std::string 好像没有数组的构造函数,然后转换成了 char*

    大概是这样
    About     Help     Advertise     Blog     API     FAQ     Solana     921 Online   Highest 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 159ms UTC 22:01 PVG 06:01 LAX 15:01 JFK 18:01
    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