为什么我可以将字符分配给字符串对象而不是字符串对象的向量?

Why can I assign characters to string objects but not to vectors of string objects?

C++ Primer 说字符和字符串文字可以转换为 strings。

我尝试将字符分配给 string 作为:

std::string s;
s = 's';

它没有给我任何错误。

但是,当我尝试将字符分配给 string 对象的 vector 时:

std::vector<std::string> svec;
svec = {'a', 'b'};

它给了我一个错误。为什么?

第一个表达式中的字符文字赋值有效,因为 std::string class has an overload for the assignment operator 采用 char.

第二个表达式中的字符文字参数不能像字符串文字那样隐式转换为字符串(即 svec = {"a", "b"}),因为 std::string has a constructor for const char* but not for char:

表达式:

svec = {"a", "b"};

使用构造函数

string (const char* s);

表达式:

svec = {'a', 'b'};

无法工作,因为不存在采用单个字符参数的构造函数。

它有一个构造函数,它接受一个 initializer_list(正如你在前面的 link 中看到的):

string (initializer_list<char> il); 

Available since C++11.

因此,要使用字符文字初始化 std::string,您需要使用大括号(即 braced initializer list):

std::vector<std::string> svec;
svec = {{'a'}, {'b'}};

如您所知,这将在向量的前 2 个位置初始化 2 个字符串,一个包含 "a",另一个包含 "b"

对于向量第一个位置的单个字符串,您可以使用:

svec = {{'a','b'}};

C++ Primer says that character and string literals may be converted to strings.

C 风格字符串文字可以隐式转换为 std::string,但 chars 不能。这与赋值不同。

s = 's'; 有效,因为 std::string 有一个超载的 operator= 占用 char.

  1. Replaces the contents with character ch as if by assign(std::addressof(ch), 1)

svec = {'a', 'b'}; 不起作用,因为 std::vector 仅重载了 operator=s taking std::vector or std::initializer_list, both of them can't be constructed from braced-init-list {'a', 'b'}. You might think the overload taking std::initializer_list<std::string> could be used for this case, but char can't be converted to std::string implicitly (std::string doesn't have such converting constructor 占用 char),然后 std::initializer_list<std::string> 无法从 [= 构造25=].

作为解决方法,您可以将代码更改为

svec = {"a", "b"};

"a" 是类型 const char[2] 并且衰减 const char*,可以隐式转换为 std::string(通过 std::string's converting constructorconst char*),然后 std::initializer_list<std::string>{"a", "b"} 构造并传递给 std::vector::operator=。当然 svec = {std::string("a"), std::string("b")}; (或 svec = {"a"s, "b"s};) 也可以, std::initializer_list<std::string> 将直接构造而无需隐式转换为 std::string.

考虑以下示例:

#include <initializer_list>
#include <vector>
#include <string>
#include <type_traits>

int main() {
    std::vector<std::string> svec;
    
    // Non-ambigously deduced to std::initializer_list<char>.
    auto a = {'a', 'b'};
    static_assert(std::is_same_v<std::initializer_list<char>, decltype(a)>);
    
    // (A)
    // error: no match for 'operator=' (... std::vector<std::string> and 'std::initializer_list<char>')
    //svec = a;  
    //svec = std::initializer_list<char>{'a', 'b'};
    
    // error:
    // error: unable to deduce 'std::initializer_list<auto>' from '{{'a', 'b'}}'
    //auto b = {{'a', 'b'}};
    
    // (B)
    // OK. 
    // After all the following copy assingnments, svec.size() is 1 (a single "ab" element).
    svec = {{'a', 'b'}};
    svec = std::initializer_list<std::string>{{'a', 'b'}};
    svec = std::initializer_list<std::string>{std::initializer_list<char>{'a', 'b'}};
    svec = std::initializer_list<std::string>{"ab"};
}

(A) 处的错误消息是不言自明的:std::vector<std::string> 没有复制赋值运算符来为其分配 std::initializer_list<char> 参数. std::vector<T,Allocator>::operator= 唯一可行的重载需要类型为 std::initializer_list<T>:

的参数
vector& operator=( std::initializer_list<T> ilist );

Replaces the contents with those identified by initializer list ilist.

但是本例中的 Tstd::string,因此这种重载是不可行的。

(B) 中,另一方面,我们使用嵌套大括号初始化语法 {{'a', 'b'}} 这样 inner 大括号初始化列表表示一个 std::initializer_list<char>,它可以使用 the following std::string constructor:

basic_string( std::initializer_list<CharT> ilist,
                const Allocator& alloc = Allocator() );

创建一个single std::string temporary,它是外括号初始化列表的一部分,利用上面提到的std::vector复制赋值运算符:

vector& operator=( std::initializer_list<T> ilist )

(B)的不同变体的结果是相同的:复制分配给svec向量一个单向量std::string元素.

最后,请注意,如果您要使用 " 分隔的 字符串 的花括号初始化列表复制赋值给 svec,结果是复制分配一个包含 两个元素的向量:

svec = {"a", "b"};  // svec.size() is now 2

因为我们没有直接创建 两个元素 std::initializer_list<std::string>,与上面 std::initializer_list<char> 几个 字符相比 结果是 单个字符串 。即,单个 std::string 对象也是 一个包含(零个或)多个 char 元素的容器

理解这一点的关键是初始化列表。

首先,请注意这是行不通的:

  std::string s('a');

但这确实:

  std::string s{'a'};

原因是第一个需要一个接受单个 char 的构造函数,但 std::string 没有这样的构造函数。另一方面,第二个创建一个 initializer_list<char>std::string 确实有一个构造函数。

完全相同的推理适用于

  std::vector<std::string>> vs{ 'a', 'b' };

对比

  std::vector<std::string>> vs{ {'a'}, {'b'} };

第一个想要使用一个不存在的 std::string ctor 获取 char,第二个使用初始化列表。

至于原代码,原因是

  std::string s;
  s = 'a';

的工作原理是虽然 std::string 缺少接受 char 的构造函数,但它确实有一个接受 char.

的赋值运算符