19

How can I do the equivalent of:

#include <vector>

size_t bufferSize = 1024 * 1024;
std::vector<unsigned char> buffer(bufferSize, ' ');

With list (curly braced) initialization?

When I try to do the following:

#include <vector>

size_t bufferSize = 1024 * 1024;
std::vector<unsigned char> buffer {bufferSize, ' '};

It wrongly interprets bufferSize as the value to be stored in the first index of the container (i.e. calls the wrong std::vector constructor), and fails to compile due to invalid narrowing conversion from unsigned int (size_t) to unsigned char.

cigien
  • 57,834
  • 11
  • 73
  • 112
not an alien
  • 651
  • 4
  • 13
  • That's how brace-initialization works with `std::initializer_list` constructors. It always prefer those constructors, hence it tries to invoke it and fails – Fureeish Nov 29 '18 at 15:07
  • 9
    Why do you insist on doing this with "curly brace initialization"? – Max Langhof Nov 29 '18 at 15:08
  • Basically a dupe of: https://stackoverflow.com/questions/47775439/curly-braces-constructor-prefers-initializer-list-over-better-match-why – NathanOliver Nov 29 '18 at 15:09
  • You cannot and it is big downside of idea "use curly brackets initialization everywhere". If would just use `bufferSize` issue would be even worse – Slava Nov 29 '18 at 15:09
  • 5
    @MaxLanghof Well, it is called uniform initialization. One could be lead to believe it should be the preferred way to initialize an object ;) – NathanOliver Nov 29 '18 at 15:10
  • @NathanOliver Well I never quite got why C++ insisted on doing [this](https://xkcd.com/927/) to [C++ initialization](https://www.reddit.com/r/ProgrammerHumor/comments/8nn4fw/forrest_gump_learns_c/)... – Max Langhof Nov 29 '18 at 15:11
  • 4
    @NathanOliver Related https://xkcd.com/927/ – llllllllll Nov 29 '18 at 15:12
  • 2
    @OP Here is a very good talk on the nightmare of C++ initialization: https://www.youtube.com/watch?v=7DTlWPgX6zs – NathanOliver Nov 29 '18 at 15:12
  • 2
    @MaxLanghof because some recommend to use it everywhere as it is "safer" (does not have most vexing parse issue). In reality it is more dangerous. – Slava Nov 29 '18 at 15:13
  • Lately I've gotten into the habit of using `auto var = type(stuff);` as it is easier for me to control which constructor I want to call. One of these days we might actually get this fixed though. – NathanOliver Nov 29 '18 at 15:15
  • Related https://stackoverflow.com/questions/33444814/is-it-good-habit-to-always-initialize-objects-with – Slava Nov 29 '18 at 15:19
  • 2
    tldr; Use `vector{...}` to initialize from a list (including 0 elements), and `vector(...)` to call all other constructors. "Universal" doesn't mean "this is the replacement". It just means you can now list initialize on all objects, rather than just some of them. – Mooing Duck Nov 29 '18 at 23:53
  • @MaxLanghof another reason for wanting to use curly brace initialization is non-static member initialization via a default member initializer which is either a brace or equal initializer. And when a - say - `std::vector` member should be constructed with a certain size, one can't just write something as compact as `std::vector foo { some_size, 42 }` which actually creates the vector member with `some_size` elements which are initialized to 42. – maxschlepzig May 21 '21 at 17:53

2 Answers2

17

Short answer: you don't.

This is not a problem with uniform initialization per se, but with std::initializer_list. There is a special rule in overload resolution that always gives priority to constructors taking std::initializer_list if list-initialization is used, regardless of the existence of other constructors which might require less implicit conversions.


I would suggest using

std::vector<unsigned char> buffer(bufferSize, ' ');

or, if you really want to use list-initialization, create your wrapper around std::vector that provides constructor overloads that do the right thing.

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
7

The two relevant overload of std::vector are:

explicit vector( size_type count, 
                 const T& value = T(),
                 const Allocator& alloc = Allocator()); //(1)
vector( std::initializer_list<T> init, 
        const Allocator& alloc = Allocator() ); // (2)

These two overload has clear meaning, where the second is used to initialize the vector with the elements of the std::initializer_list.

Overload resolution prefer initializer-list constructors when list-initialization is used.

Narrowing conversions are not allowed with list-initialization, you're trying to create a std::vector with T=unsigned char but the deduced T for the std::initializer_list parameter is T= unsigned long which will involve a narrowing conversion (not allowed).

Jans
  • 11,064
  • 3
  • 37
  • 45