最近在学 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++只允许进行一次隐式转换,不知是否正确
1
nightwitch 2020-05-24 12:44:47 +08:00
第一个函数你是从"const char [6]" 类型转换到 Stack<std::string>类型, 这个类型系统肯定会报错的, 这也不是构造函数,而是 operator=函数.
|
2
Tony042 OP @nightwitch operator= 是 copy assignment 函数,我这里就是新构造一个 Stack 类型的 stringStack,没有赋值操作,事实上我在写这个例子时候把 operator=delete 的了,所以这里就是构造函数,之所以不用写类型参数,试用了 C++17 的自动推导,我很疑惑为啥俩编辑器一个通过一个不通过
|
3
Tony042 OP @nightwitch 不好意思还要再杠一下,是 const char[7]不是 const char[6]因为这个数组最后会自动补一个'\n'
|
4
lechain 2020-05-24 13:11:01 +08:00 1
很久没接触过 C++了,提个我自己的猜测。
写法二一定会走 “Stack(T const &elem) : elems({elem}) {}”构造的,这个是显示声明实现了的,所以铁定是没有问题的 写法一是走的 fault 构造的,二 default 构造你并没有显示实现,所以这个构造得依赖于编译器,编译器以不一样的方式隐式的实现,就会产生不一样的结果 、char*格式的 string 和 C++ class String 自动类型转换也依赖于其默认构造函数,这又是一个依赖于编译器隐式实现的默认构造 你也说了,写法一存在两种类型转换,但是考虑到这两种转换都是隐式转换,很显然你无法对依赖于编译器实现的隐式转换报有过多的期望。 |
5
damingxing 2020-05-24 13:14:33 +08:00 1
不用纠结,直接用第二种方法就行了。。
|
6
Tony042 OP @lechain 谢谢你,其实还有个问题 Stack(T const&)这里是引用传递不是值传递所以不应该触发 char const[7]到 char const *的类型衰变( type decay ),但是很奇怪的是这里 GCC 和 MSVC 都编译过去了,没有报错
|
7
constexpr 2020-05-24 13:31:09 +08:00 1
CTAD 太复杂, 不充分了解 CTAD 是没法很好回答这个问题的- -
那个我认为应该是 GCC 的一个 BUG, 他在拷贝初始化的时候没有使用用户定义的推导指引. 你的第二个问题, 推导指引是这样工作的,他选择了一个合适的推导指引(用户定义或者隐式的),然后推导出类型,这里是 std::string, 然后把这个类型带到类里面,那么这里的 T 就是 string 了, 所以 那个构造函数是 Stack(const std::string &elem)... 你可以用 typeid 确认一下确实是这个类型. |
9
constexpr 2020-05-24 13:35:06 +08:00 1
@constexpr whatever. 反正是用户定义的推到指引
C++17 这个功能具恶心, std::vector a{1, 1.2}合法, std::vector b{1.2, 1}报错 你想想这是为什么 |
11
secondwtq 2020-05-24 13:36:15 +08:00 1
https://en.cppreference.com/w/cpp/language/implicit_conversion 搜 user-defined conversion
|
12
secondwtq 2020-05-24 13:40:23 +08:00 1
另外第一个是 copy initialization
const char *和 std::string 之间的转换是标准定义的,不是 implementation-defined |
13
constexpr 2020-05-24 14:03:57 +08:00 1
@constexpr 我重新审视了一下,第一个 GCC 或许不是 BUG
因为是拷贝构造, 所以等号右边得是 Stack 类型, 而他根据用户定义是推到指引, 最终会引发 char const[7]->String->Stack 的转换,这是不允许的. PS: 数组最后一位是'\0', 而非'\n' |
14
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 的直接转换。 |
16
Tony042 OP @constexpr #9 能解释下为什么 std::vector a{1, 1.2}合法, std::vector b{1.2, 1}报错么
|
17
Tony042 OP @msg7086 这个地方,应该是把 Stack(T const &elem)改成 Stack(T const elem)就可以编译通过了,但是 gcc 如果加上-std=c++2a 的话用 ref 版的也可以通过
|
18
Tony042 OP @secondwtq 你好,谢谢提供的资料,我看了下,貌似这个地方不是 user-defined conversion,由于 Stack 这个类是个模板,在 C++17 以前必须写成 Stack<std::string> s("bottom"),显式指明类模板参数,但是 C++17 支持 class template argument deduction,所以在一部分情况下编译器会自动推导类模板参数,但是也会经常推导出错,比如如果没有最后一行类模板推导指引,一般来说编译器会将 T 推导至 char const[7]而不是推向 std::string,这样的话就会带来很多麻烦。
|
20
constexpr 2020-05-24 22:23:31 +08:00 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 |
22
Wirbelwind 2020-05-26 12:39:37 +08:00 1
这个推导指引被视为 函数了
|
23
Wirbelwind 2020-05-26 13:26:41 +08:00 1
@Wirbelwind 之前只看了提示报错,测试不充分
--- Stack stringStack{"bottom"}; 这句可以过是因为 Stack(T const &elem),这里定义的第三个构造函数,如果把去掉是编译不过的 提示也是类型转换问题。 --- 这里的问题主要是 模板生成时不允许强制转换 |
24
Wirbelwind 2020-05-26 14:02:15 +08:00 1
Stack(char const *)->Stack<std::string>
这里因为你指引了一个模板 所以应该提供一个专用的 构造函数。 建议不要学模板这么复杂的功能,心智负担太大。 以上分析不保证正确 |
25
Tony042 OP @Wirbelwind #23 是这个构造函数在起作用,我疑惑的地方是,传入引用 ( T const& elem)为什么可以发生 type decay 也就是 char const[7] -> char const *,一般只有传入值的时候会 type decay
|
26
Wirbelwind 2020-05-27 09:18:12 +08:00
|