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

2020-05-24 11:57:20 +08:00
 Tony042

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

3018 次点击
所在节点    C++
26 条回复
nightwitch
2020-05-24 12:44:47 +08:00
第一个函数你是从"const char [6]" 类型转换到 Stack<std::string>类型, 这个类型系统肯定会报错的, 这也不是构造函数,而是 operator=函数.
Tony042
2020-05-24 12:47:47 +08:00
@nightwitch operator= 是 copy assignment 函数,我这里就是新构造一个 Stack 类型的 stringStack,没有赋值操作,事实上我在写这个例子时候把 operator=delete 的了,所以这里就是构造函数,之所以不用写类型参数,试用了 C++17 的自动推导,我很疑惑为啥俩编辑器一个通过一个不通过
Tony042
2020-05-24 12:49:39 +08:00
@nightwitch 不好意思还要再杠一下,是 const char[7]不是 const char[6]因为这个数组最后会自动补一个'\n'
lechain
2020-05-24 13:11:01 +08:00
很久没接触过 C++了,提个我自己的猜测。

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

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

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

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

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

PS: 数组最后一位是'\0', 而非'\n'
msg7086
2020-05-24 14:17:01 +08:00
@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
2020-05-24 21:37:41 +08:00
@constexpr 谢谢指正,是'\0'
Tony042
2020-05-24 21:40:36 +08:00
@constexpr #9 能解释下为什么 std::vector a{1, 1.2}合法, std::vector b{1.2, 1}报错么
Tony042
2020-05-24 21:42:11 +08:00
@msg7086 这个地方,应该是把 Stack(T const &elem)改成 Stack(T const elem)就可以编译通过了,但是 gcc 如果加上-std=c++2a 的话用 ref 版的也可以通过
Tony042
2020-05-24 21:51:25 +08:00
@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
2020-05-24 21:59:09 +08:00
@secondwtq 刚才又想了下,你说的是有道理的,谢谢
constexpr
2020-05-24 22:23:31 +08:00
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

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/674867

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX