Introduction
Imagine the following declaration, and usage:
struct A {
A (std::initializer_list<std::string>);
};
A {{"a" }}; // (A), initialization of 1 string
A {{"a", "b" }}; // (B), initialization of 1 string << !!
A {{"a", "b", "c"}}; // (C), initialization of 3 strings
In (A) and (C), each c-style string is causing the initialization of one (1) std::string, but, as you have stated in your question, (B) differs.
The compiler sees that it’s possible to construct a std::string using a begin- and end-iterator, and upon parsing statement (B) it will prefer such construct over using "a"
and "b"
as individual initializers for two elements.
A { std::string { "a", "b" } }; // the compiler's interpretation of (B)
Note: The type of
"a"
and"b"
ischar const[2]
, a type which can implicitly decay into achar const*
, a pointer-type which is suitable to act like an iterator denoting either begin or end when creating a std::string.. but we must be careful: we are causing undefined-behavior since there is no (guaranteed) relation between the two pointers upon invoking said constructor.
Explanation
When you invoke a constructor taking an std::initializer_list using double braces {{ a, b, ... }}
, there are two possible interpretations:
-
The outer braces refer to the constructor itself, the inner braces denotes the elements to take part in the std::initializer_list, or:
-
The outer braces refer to the std::initializer_list, whereas the inner braces denotes the initialization of an element inside it.
It’s prefered to do 2) whenever that is possible, and since std::string
has a constructor taking two iterators, it is the one being called when you have std::vector<std::string> {{ "hello", "there" }}
.
Further example:
std::vector<std::string> {{"this", "is"}, {"stackoverflow"}}.size (); // yields 2
Solution
Don’t use double braces for such initialization.