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

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

C-style string literals can convert to std::string implicitly, but chars can't. That's different from assignment.

s = 's'; works because std::string has an overloaded operator= taking char.

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

svec = {'a', 'b'}; doesn't work because std::vector only has overloaded 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 taking a char), then std::initializer_list<std::string> failed to be constructed from {'a', 'b'}.

As the workaround, you can change the code to

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

"a" is of type const char[2] and decays const char*, which could be converted to std::string implicitly (via the std::string's converting constructor taking const char*), then std::initializer_list<std::string> gets constructed from {"a", "b"} and passed to std::vector::operator=. Of course svec = {std::string("a"), std::string("b")}; (or svec = {"a"s, "b"s};) works too, std::initializer_list<std::string> will be constructed directly without such implicit conversion to std::string.


The assignment of a character literal in the first expression works because the std::string class has an overload for the assignment operator that takes char.

The character literal arguments in the second expression cannot be implicitly converted to strings, like string literals can (i.e. svec = {"a", "b"}), because std::string has a constructor for const char* but not for char:

The expression:

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

uses the constructor

string (const char* s);

The expression:

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

can't work because no such constructor exists that takes a single character argument.

What it does have is a constructor that takes an initializer_list (as you can see in the previous link):

string (initializer_list<char> il); 

Available since C++11.

So to initialize std::string with character literals you need to use curly brackets (i.e. braced initializer list):

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

This will, as you know, initialize 2 strings in the first 2 positions of the vector one contains "a" and the other "b".

For a single string in the first position of the vector you can use:

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

The key to understanding this is initializer lists.

First, note that this does not work:

  std::string s('a');

but this does:

  std::string s{'a'};

The reason is that the first one would require a ctor that takes a single char, but std::string does not have such a constructor. The second, on the other hand, creates an initializer_list<char>, for which std::string does have a ctor.

The exact same reasoning applies to

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

versus

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

The first wants to use a non-existent std::string ctor taking a char, and the second uses initializer lists.

As for the original code, the reason that

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

works is that while std::string lacks a ctor taking a char, it does have an assignment operator which takes a char.