0

I recently got bit in the butt with this code:

std::size_t s = 10;
std::vector<int> v{s};

Rather than initialize with a size of 10, this initializes with a size of 1 with one element 10. However, the vector has an explicit constructor that takes a std::size_t. With all the hype about "use braces everywhere", I would expect many people have fallen into this trap. This could be avoided if the compiler simply warned that we were trying to turn a size_t into an int.

Why isn't the compiler required to do this?

EDIT: My original code had const std::size_t s. Apparently none of the compilers I use warn unless I remove the const. Is this a bug?

MWiesner
  • 8,868
  • 11
  • 36
  • 70
  • there's no information loss – valdo Sep 29 '15 at 17:28
  • 2
    [fails to compile here](http://coliru.stacked-crooked.com/a/4e2010051a6bc5ff) – NathanOliver Sep 29 '15 at 17:28
  • 3
    Turn the warning level up, with MSVS2015 that code gives me "error C2398: Element '1': conversion from 'size_t' to 'int' requires a narrowing conversion" and "warning C4267: 'initializing': conversion from 'size_t' to 'int', possible loss of data" – Blastfurnace Sep 29 '15 at 17:29
  • @NathanOliver Both GCC and Clang don't warn if I use a `const std::size_t` instead. – user5389903 Sep 29 '15 at 17:32
  • See [here](http://stackoverflow.com/a/27877127/2747160): It's not a bug. As long as s is const, the compiler can (and in fact will) verify, that 10 can be stored as int without any losses. – m8mble Sep 29 '15 at 17:37
  • @m8mble I posted an answer to that effect moments before your comment. – user5389903 Sep 29 '15 at 17:38

2 Answers2

2

No it is not a bug. See [dcl.init.list]/7 of N3337:

A narrowing conversion is an implicit conversion

...

  • from an integer type or unscoped enumeration type to an integer type that cannot represent all the values of the original type, except where the source is a constant expression and the actual value after conversion will fit into the target type and will produce the original value when converted back to the original type.

Therefore the code is valid so long as s is const.

1

Rather than initialize with a size of 10, this initializes with a size of 1 with one element 10

First initializer-list are gready:

§13.3.1.7 [over.match.list]/p1:

When objects of non-aggregate class type T are list-initialized (8.5.4), overload resolution selects the constructor in two phases:

  • Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of the initializer list as a single argument.
  • If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.

If the initializer list has no elements and T has a default constructor, the first phase is omitted. In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.

So the compiler will first try to create the vector using

vector( std::initializer_list<T> init, const Allocator& alloc = Allocator() );

As pointed out by user5389903 since you have a const std::size_t s and its value is within the range of an int it can convert {s} into a std::initializer_list<int>. now since we have a valid std::initializer_list the std::initializer_list constructor will be called.

Community
  • 1
  • 1
NathanOliver
  • 171,901
  • 28
  • 288
  • 402