2
#include <vector>

using std::size_t;

struct Foo
{
    Foo(size_t i, char c) {}
};

Foo Bar1()
{
    size_t i = 0;
    char c = 'x';
    return { i, c }; // good
}

std::vector<char> Bar2()
{
    size_t i = 0;
    char c = 'x';
    return { i, c }; // bad
}

https://wandbox.org/permlink/87uD1ikpMkThPTaw

warning: narrowing conversion of 'i' from 'std::size_t {aka long unsigned int}' to 'char' inside { }

Obviously it tries to use the initializer_list of vector. But why doesn't it use the better match vector<char>(size_t, char) ?

Can i use the desired constructor in a return statement without writing the type again?

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
lars
  • 658
  • 4
  • 11
  • 1
    See for example [here](http://en.cppreference.com/w/cpp/language/list_initialization), that after the special cases for aggregate types and empty braced-init-lists, it considers `std::initializer_list` constructors before other constructor overloads. – Useless Dec 12 '17 at 14:57
  • Related? https://stackoverflow.com/q/37682392/2642059 – Jonathan Mee Dec 12 '17 at 15:44

1 Answers1

5

Because initializer_list constructors, if at all possible, take precedence over other constructors. This is to make edge cases less confusing - specifically, this particular vector constructor that you expect it to use was deemed too easily selected by accident.

Specifically, the standard says in 16.3.1.7 "Initialization by list-initialization" [over.match.list] (latest draft, N4687):

(1) When objects of non-aggregate class type T are list-initialized such that 11.6.4 specifies that overload resolution is performed according to the rules in this section, overload resolution selects the constructor in two phases:

  • Initially, the candidate functions are the initializer-list constructors (11.6.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.

So if you do std::vector<char>( i, c ), this section does not apply at all, since it isn't list-initialization. Normal overload resolution is applied, the (size_t, char) constructor is found, and used.

But if you do std::vector<char>{ i, c }, this is list-initialization. The initializer list constructors are tried first, and the (initializer_list<char>) constructor is a match (even though it involves the narrowing conversion from size_t to char), so it is used before the size+value constructor is ever considered.

So to answer the edited-in question: no, you can't create the vector without naming its type. But in C++17 you can use class template argument deduction and simply write return std::vector(i, c);

Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157
  • That makes sense, but i don't understand why initializer_list is "matched first". Could you please explain why return std::vector( i, c ) works fine but std::vector{ i, c } doesn't? That would be awesome. I have problems finding the relevant text passages in cppreference, that explain the differences between curly braces and usual constructor syntax. – lars Dec 12 '17 at 14:58
  • Perfect. Thanks a lot. – lars Dec 12 '17 at 15:06
  • @lars Added an answer for the follow-up question too. – Sebastian Redl Dec 12 '17 at 15:09