Reputation: 217
I have the following code:
#include <iostream>
#include <vector>
struct C {
int a;
C() : a(0) {}
C(int a) : a(a) {}
};
std::ostream &operator<<(std::ostream &os, const C &c) {
os << c.a;
return os;
}
using T = std::vector<C>;
int main() {
auto v = T({5});
for (const auto &a : v) {
std::cout << a << ", ";
}
std::cout << std::endl;
return 0;
}
If I use g++, it prints:
5,
If I use MS compiler, it prints:
0, 0, 0, 0, 0,
Why are the results different?
Without ()
around {5}
, the results, of course, are the same.
Upvotes: 20
Views: 870
Reputation: 51894
This is probably a bug in the g++ compiler§ (not in the MSVC compiler). The type of the v
variable is (or should be, according to the Standard) a std::vector<C>
containing 5 default-initialized elements.
Why? Because you use direct initialization (round brackets) rather than list initialization (curly braces, as in auto v = T{ 5 }
). This direct initialization means that a constructor is called, and there is no constructor for std::vector<T>
that takes a single, T
argument.
This source of confusion is mentioned on cppreference:
Note that the presence of list-initializing constructor (10) means list initialization and direct initialization do different things:
Now, you may think that the compiler should be using that list-initializing constructor (as g++ does) … but it shouldn't be doing so! That constructor's argument is not of type T
but of type std::initializer_list<T>
; so, to call that constructor (using direct initialization), you would need auto v = T({ { 5 } });
(note the extra set of braces). †
For your code, auto v = T({ 5 });
, the closest-matched constructor is version #3 on the linked cppreference page (with the 2nd and 3rd arguments defaulted), so the { 5 }
is treated as a size_t
value for the count of elements initialized as T()
. In fact, according to that same page, the defaulted 2nd argument version has been removed since C++11 – so the only match for your call is constructor version #4, which produces the same result.
In fact, clang (which gives the same result as MSVC for your code) warns about this (see it on Compiler Explorer):
warning : braces around scalar initializer [-Wbraced-scalar-init]
There are a number of ways you can force the initialization to a single-element vector with a {5}
member. The simplest is to use list-initialization:
auto v = T{ { 5 } }; // Or even: auto v = T{ 5 };
Another is to add an explicit count
argument:
auto v = T(1, { 5 }); // Or just: auto v = T(1, 5);
§ I do not claim to be an authority when it comes to the rules a compiler should follow in order to conform to the C++ Standard(s), so I am prepared to accept that this may be a (rare) ambiguity in the Standard, rather than a bug or defect in the g++ compiler.
† Note that, although { 5 }
can be used as a valid std::initializer_list<C>
(as it is in the case of auto v = T{ 5 };
), in terms of overload resolution for function calls (including those to constructors), it is a better match for a single size_t
value; see Single-Element-Vector Initialization in a Function Call (especially the top answer).
Upvotes: 13