Walton1128/CPP-Templates-2nd--

P38-Deduction Guides

Opened this issue · 7 comments

这里对Stack stringStack = "bottom";不合法的情景解读有误
image
原文的说法是:

However, by language rules, you can't copy initialize (initialize using =) an object by passing a string literal to a constructor expecting a std::string. So you have to initialize the stack as follows:

Stack stringStack{"bottom"};    //Stack<std::string> deduced and valid

直译如是:
然而,根据语言规则,你无法通过传递一个字符串字面量来拷贝初始化(使用=初始化语法)一个期望传递std::string的对象。因此,你不得不按下面的方式初始化:Stack stringStack{"bottom"}; //Stack<std::string> deduced and valid

但我觉着这里作者的说法也不太对,上面的Stack stringStack = "bottom";之所以不合法,是因为类的初始化赋值语句实际上会调用copy constructor,但是copy constructor需要的是一个Stack<std::string>(Deduction Guides推断的Tstd::string),而const char [7]("bottom"字面量是const char [7]类型)无法转换成non-scalar typeStack<std::string>

我写了一段代码来验证:

template<typename T>
class Stack {
  private:
    std::vector<T> elems;
  public:
    Stack(const T& elem) : elems({elem}) {}
};

Stack(char const*) -> Stack<std::string>;

int main()
{
  Stack stringStack = "bottom";
  return 0;
}

用gcc 7.3.1编译报错如下:

$ g++ main.cpp -o main -std=c++17
main.cpp: In function ‘int main()’:
main.cpp:38:23: error: conversion from ‘const char [7]’ to non-scalar type ‘Stack<std::basic_string<char> >’ requested
   Stack stringStack = "bottom";
                       ^~~~~~~~

std::basic_string是std::string的alias

可以看到这里需要一个隐式转换的方法(即接受const char [7]的一个ctor但显然没有),所以编译失败。
但终归是能看到上面的Deduction Guide语句生效了,因为确实编译错误里认为stringStack是Stack<std::basic_string<char> >,尽管"bottom"是const char [7],与const char*有些区别,但依然可以被推导为std::string。当我把“bottom”换成const char *str = "bottom"; Stack stringStack = str;,再次编译,错误变成:

main.cpp: In function ‘int main()’:
main.cpp:36:23: error: conversion from ‘const char*’ to non-scalar type ‘Stack<std::basic_string<char> >’ requested
   Stack stringStack = str;
                       ^~~

也是符合预期的,因为被我手工decay成了const char *,Deduction Guide依然生效。

当把Deduction Guide语句删除后,并重新改为Stack stringStack = "bottom";编译错误迥然不同了:

main.cpp: In instantiation of ‘Stack<T>::Stack(const T&) [with T = char [7]]’:
main.cpp:36:23:   required from here
main.cpp:28:40: error: no matching function for call to ‘std::vector<char [7], std::allocator<char [7]> >::vector(<brace-enclosed initializer list>)’
     Stack(const T& elem) : elems({elem}) {}
                                        ^
......

省略了后面一大段,只看前面就可以看到T被推断为char[7],这里接受const T&参数的ctor语法编译就不通过了。而如果是const char *str = "bottom"; Stack stringStack = str;则可以编译通过,这也是符合模板推断预期的。

至于后面的花括号直接初始化语法当然是可行的(这里因为既不是aggregate class也没有initializer_list做参数的ctor,所以是匹配某个合适的ctor),这个就不展开了,实际上,用小括号语法直接调用构造器也可以完成const char*->std::string的推断:Stack stringStack("bottom");

此外,后面这段语法我也没太理解作者的意思。

Note that, if in doubt, class template argument deduction copies. After declaring stringStack as Stackstd::string the following initializations declare the same type instead of initializing a stack by elements that are string stacks.

前半句没get到意思,后面应该是说下面的那几种初始化语句都是通过调用copy constructor定义了同样的type,倒是没什么疑问,只是总觉着作者说了什么我没有get到

感谢,受教了。

感觉作者的意思等于直接说出了结果:在copy initialize一个对象的时候,不能将string literal传递给一个接受std::string类型参数的构造函数。。暗含的意思是没有定义接受const char *作为参数的构造函数。。如果定义了接受const char *的构造函数,在执行Stack stringStack = “bottom”的时候,会先用Stack(const char *)生成一个临时对象,然后调用copy constructor完成copy initialize。。

而之所以stringStack("bottom")或者stringStack{“bottom”}可以,是因为此处已经明确了是执行一个构造函数的调用,编译器会帮忙完成"bottom"到std::string("bottom")的转换。。

之所以stringStack = “bottom”不可以,是因为在没有Stack(const char *)构造函数的情况下,需要执行两部隐式转换,一步是const char *到std::string. 另一步是std::string 到Stack。但是这种两步的转换不被编译器允许(C++ Primer第五版7.5.4节)。

不知道上述分析和你的意思是不是一致?等达成一致我去更新翻译内容。

感谢,受教了。

感觉作者的意思等于直接说出了结果:在copy initialize一个对象的时候,不能将string literal传递给一个接受std::string类型参数的构造函数。。暗含的意思是没有定义接受const char *作为参数的构造函数。。如果定义了接受const char *的构造函数,在执行Stack stringStack = “bottom”的时候,会先用Stack(const char *)生成一个临时对象,然后调用copy constructor完成copy initialize。。

而之所以stringStack("bottom")或者stringStack{“bottom”}可以,是因为此处已经明确了是执行一个构造函数的调用,编译器会帮忙完成"bottom"到std::string("bottom")的转换。。

之所以stringStack = “bottom”不可以,是因为在没有Stack(const char *)构造函数的情况下,需要执行两部隐式转换,一步是const char *到std::string. 另一步是std::string 到Stack。但是这种两步的转换不被编译器允许(C++ Primer第五版7.5.4节)。

不知道上述分析和你的意思是不是一致?等达成一致我去更新翻译内容。

理解是一致的。另外补充一句stringStack("bottom")或者stringStack{“bottom”}有效是在Deduction Guide(const char *->std::string)的前提下,否则会直接推断成const char *const char [7](传值或传引用),就谈不上到std::string的变化了,当然了,如果是直接指定Stack<std::string> stk("bottom");或是Stack<std::string> stk{"bottom"};就不需要了

感谢,受教了。
感觉作者的意思等于直接说出了结果:在copy initialize一个对象的时候,不能将string literal传递给一个接受std::string类型参数的构造函数。。暗含的意思是没有定义接受const char *作为参数的构造函数。。如果定义了接受const char *的构造函数,在执行Stack stringStack = “bottom”的时候,会先用Stack(const char *)生成一个临时对象,然后调用copy constructor完成copy initialize。。
而之所以stringStack("bottom")或者stringStack{“bottom”}可以,是因为此处已经明确了是执行一个构造函数的调用,编译器会帮忙完成"bottom"到std::string("bottom")的转换。。
之所以stringStack = “bottom”不可以,是因为在没有Stack(const char *)构造函数的情况下,需要执行两部隐式转换,一步是const char *到std::string. 另一步是std::string 到Stack。但是这种两步的转换不被编译器允许(C++ Primer第五版7.5.4节)。
不知道上述分析和你的意思是不是一致?等达成一致我去更新翻译内容。

理解是一致的。另外补充一句stringStack("bottom")或者stringStack{“bottom”}有效是在Deduction Guide(const char *->std::string)的前提下,否则会直接推断成const char *const char [7](传值或传引用),就谈不上到std::string的变化了

谢谢,我是用实例化之后的版本做的测试。晚上更新一下翻译。到时候烦请再帮忙review一下。

感谢,受教了。
感觉作者的意思等于直接说出了结果:在copy initialize一个对象的时候,不能将string literal传递给一个接受std::string类型参数的构造函数。。暗含的意思是没有定义接受const char *作为参数的构造函数。。如果定义了接受const char *的构造函数,在执行Stack stringStack = “bottom”的时候,会先用Stack(const char *)生成一个临时对象,然后调用copy constructor完成copy initialize。。
而之所以stringStack("bottom")或者stringStack{“bottom”}可以,是因为此处已经明确了是执行一个构造函数的调用,编译器会帮忙完成"bottom"到std::string("bottom")的转换。。
之所以stringStack = “bottom”不可以,是因为在没有Stack(const char *)构造函数的情况下,需要执行两部隐式转换,一步是const char *到std::string. 另一步是std::string 到Stack。但是这种两步的转换不被编译器允许(C++ Primer第五版7.5.4节)。
不知道上述分析和你的意思是不是一致?等达成一致我去更新翻译内容。

理解是一致的。另外补充一句stringStack("bottom")或者stringStack{“bottom”}有效是在Deduction Guide(const char *->std::string)的前提下,否则会直接推断成const char *const char [7](传值或传引用),就谈不上到std::string的变化了

谢谢,我是用实例化之后的版本做的测试。晚上更新一下翻译。到时候烦请再帮忙review一下。

图片
具体讨论内容放在注释里面。

无异议,感谢译者呕心沥血无私奉献

比较有趣的issue,reopen供更多人探讨