4

Why are different results printed with these two lines of code?

std::cout << std::string{6, 's'}
std::cout << std::string(6, 's')
Edgar Rokjān
  • 17,245
  • 4
  • 40
  • 67
cppxor2arr
  • 295
  • 2
  • 12

4 Answers4

9

Because std::string have a constructor taking an std::initializer_list, the first example will use that constructor to create a string object with two characters. Initialization like this is called list initialization.

The second example will create a string object with six characters, all initialized to 's'. This form of initialization is called direct initialization.

List initialization and direct initialization can be the same, except that the possible conversions for larger types to smaller types are forbidden for list initialization, and as noticed here if the class have a constructor taking an std::initializer_list.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • Then, the only way to directly initialize `std::string` with a set amount of characters using the `count, char` format is with `()`? – cppxor2arr Jun 26 '17 at 12:55
  • 1
    @6EQUJ5 - Yes. List initialization behaves like this because its semantics mean "put these exact things into the object". – StoryTeller - Unslander Monica Jun 26 '17 at 12:56
  • Oh my... I'm forced to do `std::string(count, char)` instead of `std::string{count, char}` now. – cppxor2arr Jun 26 '17 at 12:57
  • I can't believe I'm forced to call `basic_string(size_type count, CharT ch, const Allocator& alloc = Allocator())` with `()`. – cppxor2arr Jun 26 '17 at 13:01
  • 2
    @6EQUJ5 - It's hardly the end of the world. – StoryTeller - Unslander Monica Jun 26 '17 at 13:01
  • @StoryTeller Still, I'm disappointed. Will this be an unavoidable inconsistency if I have to use this this ^ constructor and I use `{}` for direct initializations? – cppxor2arr Jun 26 '17 at 13:02
  • 1
    Direct and list initialization aren't disjoint, they're orthogonal. `string a{6, 's'}` is both direct and list. `string b = {6, 's'}` is list but not direct. – Barry Jun 26 '17 at 13:09
  • @6EQUJ5 - This behavior only crops up if the class has a constructor taking a `std::initializer_list`. So you can still use uniform initialization syntax almost everywhere. – StoryTeller - Unslander Monica Jun 26 '17 at 13:10
  • 1
    @6EQUJ5 Just a FYI, if you use a named variable, instead of a constant the compiler can inspect, it will at least give you a compiler error: `int var = 6; std::cout << std::string{var, 's'};` should result in *warning: narrowing conversion of 'var' from 'int' to 'char' inside {}* – NathanOliver Jun 26 '17 at 13:14
  • @NathanOliver That's how I came to ask this question. I tested pretty much the same code. – cppxor2arr Jun 26 '17 at 13:16
  • @6EQUJ5 Oh, OK. Just wanted let you know if you didn't. – NathanOliver Jun 26 '17 at 13:17
  • @6EQUJ5 if you want to call a particular constructor, it's often easier to just use `(..)` and move on. The `{..}` syntax exists mostly for laziness, because it lets the compiler decide what happens. And even then, you can still say `({..})` if you really want to call the `initializer_list` constructor. – Johannes Schaub - litb Jun 26 '17 at 17:12
3

This happens because in the first case compilers prefers another overload and initializes string with symbols char(6) and 's'. You can check it by changing 6 to smth like 35 for printable char. While useful and solving most vexing parse problem, {} construction has it's caveats.

Michael232
  • 223
  • 1
  • 6
3

The reason is that different constructors are called.

std::string{6, 's'}

This code calls the constructor with initializer list:

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

So 6 is converted to char and a string which consists of two chars is printed.

std::string(6, 's')

This code calls the next constructor:

basic_string( size_type count, 
    CharT ch, 
    const Allocator& alloc = Allocator() );

so a string which consists of 6 chars is printed.

Edgar Rokjān
  • 17,245
  • 4
  • 40
  • 67
1

Initialization with {}s is called list-initialization. List-initialization follows different rules than normal initialization in several ways, but the important case here has to do with std::initializer_list.

The rule is: if you're list-initializing a class type, T, and that class type has a constructor that takes a std::initializer_list<U>, and every element in the initializer list is convertible to U, then that constructor is selected. This happens before any other constructor is even considered, even if that constructor ends up being ill-formed (e.g. due to narrowing conversion). It's important to remember this rule - std::initializer_list always takes complete precedence in this case!


The two relevant constructors for std::string here are (just assuming std::string is a type and not a class template specialization for simpilcity):

string(std::initializer_list<char> init); // #1
string(size_t count, char ch );           // #2

When you write std::string{6, 's'}, that is list-initialization, so we look to see if there's a valid std::initializer_list constructor - and there is! Both int and char are convertible to char, so it's selected. In this case, there is no narrowing conversion because 6 fits in a char, so that's selected and used. The second constructor is never even considered. Note that std::string{300, '.'} is ill-formed, because we select the std::initializer_list<char> constructor but the conversion from 300 to char is narrowing. Even though the other constructor would work, it doesn't matter, we pick std::initializer_list<char> and error.

But when you write std::string(6, 's'), that isn't list-initialization. All constructors are considered here. The std::initializer_list constructor isn't a match - you can't initialize std::initializer_list<char> from an int - but the 2nd constructor is, so it's selected. This is more normal, familiar overload resolution at work.


A good rule of thumb is - {}-initialization is for initializing aggregates (which don't have constructors anyway) or initializing containers from a particular element set or to disambiguate the most vexing parse. If you're not doing any of those things, use ()s.

Edgar Rokjān
  • 17,245
  • 4
  • 40
  • 67
Barry
  • 286,269
  • 29
  • 621
  • 977