I was reading c++ primer and came across an example of list initialization for vector where the author mentioned that if list initialization isn't possible, the compiler then looks for other ways to initialize the object. Below is such an example for vector of string.
vector<string> v8{10, "hi"};
This creates a vector of 10 elements with value "hi". This I think is because list initialization falls back on below constructor to create the vector.
vector( size_type count, const T& value, const Allocator& alloc = Allocator());
I tried similar kind of list initialization for String
std::string s1{10, 'c'};
I was expecting it to create a string "cccccccccc" because there is a constructor available for it
basic_string( size_type count, CharT ch, const Allocator& alloc = Allocator() );
But it prints only "c"? Which string constructor is getting used here?
What's coming into play here are the rules for direct-list-initialization
which has one of the syntax as
T object { arg1, arg2, ... };
Here, in the case of std::string s{10, 'c'}
, T
in the above syntax is std::string
and the rule that applies is
Otherwise, the constructors of T are considered, in two phases: All constructors that take std::initializer_list as the only argument, or as the first argument if the remaining arguments have default values, are examined, and matched by overload resolution against a single argument of type std::initializer_list
Since there is a constructor of std::string
that takes in an initializer list with the other arguments as default, namely constructor (9)
basic_string( std::initializer_list<CharT> ilist,
const Allocator& alloc = Allocator() );
that participates in this resolution since 10
can be implicitly converted to char
and what you get is a std::initializer_list<char>
of {'\n', 'c'}
of 2 characters.
In the case of std::vector<std::string> v{10. "hi"}
, T
is std::vector<std::string>
in the syntax for direct-list-initialization
as mentioned above.
Now, since 10
can't be implicitly converted to std::string
, no such std::initializer_list<std::string>
can be constructed and thus the next alternative for direct-list-initialization
is
If the previous stage does not produce a match, all constructors of T participate in overload resolution against the set of arguments that consists of the elements of the braced-init-list, with the restriction that only non-narrowing conversions are allowed.
This produces a match with constructor 3
explicit vector( size_type count,
const T& value = T(),
const Allocator& alloc = Allocator());
since neither of the conversions is non-narrowing.
Now coming to what you wanted to achieve with std::string s{10, 'c'}
, that should be done using std::string s(10, c)
where there is no list-initialization
involved.
std::string s1{10, 'c'}, s2(10, 'c');
std::cout<<'['<<s1<<']'<<'['<<s2<<']'<<'\n';
Output:
[
c][cccccccccc]
Note that printing s1
produces a new line before the c
since it's actually ['\n', 'c']
, whereas s2
is exactly what you'd expect i.e. 10 c
characters.