跳至主要內容
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?

4563博客

全新的繁體中文 WordPress 網站
  • 首頁
  • C++ 类构造时隐式转换的小疑惑
未分類
25 5 月 2020

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

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

資深大佬 : Tony042 0

最近在学 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++只允许进行一次隐式转换,不知是否正确

大佬有話說 (25)

  • 資深大佬 : nightwitch

    第一个函数你是从”const char [6]” 类型转换到 Stack<std::string>类型, 这个类型系统肯定会报错的, 这也不是构造函数,而是 operator=函数.

  • 主 資深大佬 : Tony042

    @nightwitch operator= 是 copy assignment 函数,我这里就是新构造一个 Stack 类型的 stringStack,没有赋值操作,事实上我在写这个例子时候把 operator=delete 的了,所以这里就是构造函数,之所以不用写类型参数,试用了 C++17 的自动推导,我很疑惑为啥俩编辑器一个通过一个不通过

  • 主 資深大佬 : Tony042

    @nightwitch 不好意思还要再杠一下,是 const char[7]不是 const char[6]因为这个数组最后会自动补一个’n’

  • 資深大佬 : lechain

    很久没接触过 C++了,提个我自己的猜测。

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

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

    你也说了,写法一存在两种类型转换,但是考虑到这两种转换都是隐式转换,很显然你无法对依赖于编译器实现的隐式转换报有过多的期望。

  • 資深大佬 : damingxing

    不用纠结,直接用第二种方法就行了。。

  • 主 資深大佬 : Tony042

    @lechain 谢谢你,其实还有个问题 Stack(T const&)这里是引用传递不是值传递所以不应该触发 char const[7]到 char const *的类型衰变( type decay ),但是很奇怪的是这里 GCC 和 MSVC 都编译过去了,没有报错

  • 資深大佬 : constexpr

    CTAD 太复杂, 不充分了解 CTAD 是没法很好回答这个问题的- –
    那个我认为应该是 GCC 的一个 BUG, 他在拷贝初始化的时候没有使用用户定义的推导指引.

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

  • 資深大佬 : constexpr

    @constexpr 是 CATD…

  • 資深大佬 : constexpr

    @constexpr whatever. 反正是用户定义的推到指引

    C++17 这个功能具恶心,
    std::vector a{1, 1.2}合法,
    std::vector b{1.2, 1}报错
    你想想这是为什么

  • 資深大佬 : lcdtyph

    @constexpr
    是 CTAD

  • 資深大佬 : secondwtq

    https://en.cppreference.com/w/cpp/language/implicit_conversion 搜 user-defined conversion

  • 資深大佬 : secondwtq

    另外第一个是 copy initialization
    const char *和 std::string 之间的转换是标准定义的,不是 implementation-defined

  • 資深大佬 : constexpr

    @constexpr 我重新审视了一下,第一个 GCC 或许不是 BUG
    因为是拷贝构造, 所以等号右边得是 Stack 类型, 而他根据用户定义是推到指引, 最终会引发 char const[7]->String->Stack 的转换,这是不允许的.

    PS: 数组最后一位是’’, 而非’n’

  • 資深大佬 : msg7086

    @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::__cxx11::basic_string<char>,
    std::allocator<std::__cxx11::basic_string<char> > > >’

    看上去的确是发生了 const char[] 到 Stack 的直接转换。

  • 主 資深大佬 : Tony042

    @constexpr 谢谢指正,是’’

  • 主 資深大佬 : Tony042

    @constexpr #9 能解释下为什么 std::vector a{1, 1.2}合法, std::vector b{1.2, 1}报错么

  • 主 資深大佬 : Tony042

    @msg7086 这个地方,应该是把 Stack(T const &elem)改成 Stack(T const elem)就可以编译通过了,但是 gcc 如果加上-std=c++2a 的话用 ref 版的也可以通过

  • 主 資深大佬 : Tony042

    @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

    @secondwtq 刚才又想了下,你说的是有道理的,谢谢

  • 資深大佬 : constexpr

    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

    @constexpr 这玩意真的好复杂,不知道这编译器都是咋写出来的

  • 資深大佬 : Wirbelwind

    这个推导指引被视为 函数了

  • 資深大佬 : Wirbelwind

    @Wirbelwind 之前只看了提示报错,测试不充分

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

    —
    这里的问题主要是 模板生成时不允许强制转换

  • 資深大佬 : Wirbelwind

    Stack(char const *)->Stack<std::string>
    这里因为你指引了一个模板 所以应该提供一个专用的 构造函数。

    建议不要学模板这么复杂的功能,心智负担太大。
    以上分析不保证正确

  • 主 資深大佬 : Tony042

    @Wirbelwind #23 是这个构造函数在起作用,我疑惑的地方是,传入引用 ( T const& elem)为什么可以发生 type decay 也就是 char const[7] -> char const *,一般只有传入值的时候会 type decay

文章導覽

上一篇文章
下一篇文章

AD

其他操作

  • 登入
  • 訂閱網站內容的資訊提供
  • 訂閱留言的資訊提供
  • WordPress.org 台灣繁體中文

51la

4563博客

全新的繁體中文 WordPress 網站
返回頂端
本站採用 WordPress 建置 | 佈景主題採用 GretaThemes 所設計的 Memory
4563博客
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?
在這裡新增小工具