20

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.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
Matvei
  • 217
  • 3
  • 3
    Could you paste up the compiler details please and possibly the command line too assuming you know how to extract that from MSVC. I think the MSVC compiler is at fault here, particularly if it's compiling to C++17 and onwards. – Bathsheba Apr 14 '23 at 09:44
  • `> cl /EHsc a.cpp` `Microsoft (R) C/C++ Optimizing Compiler Version 19.33.31630 for x86` – Matvei Apr 14 '23 at 10:27
  • `std::vector v = { 5 };` produces the expected result. – ChrisMM Apr 14 '23 at 12:27
  • @ChrisMM As does `auto v = T{ { 5 } };`. Not sure which compiler has the bug, though. Clang-cl does the same as MSVC. I need to think about this one. – Adrian Mole Apr 14 '23 at 17:27
  • @AdrianMole, I spent about 30 minutes looking through the standard, and thought I found relevant passages, but none of the examples in it had this type of scenario. – ChrisMM Apr 14 '23 at 18:01
  • 1
    A possible [duplicate question](https://stackoverflow.com/questions/47608388/wrong-overload-called-when-constructing-from-initializer-list-inside-parentheses), where `std::vector` is replaced by `testing::Test` – Fedor Apr 16 '23 at 20:13
  • 1
    Does this answer your question? [Wrong overload called when constructing from initializer\_list inside parentheses](https://stackoverflow.com/questions/47608388/wrong-overload-called-when-constructing-from-initializer-list-inside-parentheses) – Fedor Apr 16 '23 at 20:14

1 Answers1

13

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).

Michael M.
  • 10,486
  • 9
  • 18
  • 34
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • 1
    Why can't `{ 5 }` initialize the single argument in `vector::vector(std::initializer_list)`? `C(int)` is not explicit. Though I would think that the `vector::vector(size_type)` constructor would beat it in overload resolution anyways, but only clang seems to think that: https://godbolt.org/z/7PKM481xa – Artyer Apr 14 '23 at 21:07
  • @Artyer Yes, I think you're right. It's a case of a single-element initializer-list being converted to a `size_t`. See my edit and the newly-linked Q/A. – Adrian Mole Apr 14 '23 at 22:50
  • `GCC is actually the only compiler that implements this correctly in this case`: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109523 – Fedor Apr 15 '23 at 09:36
  • 1
    @Fedor I'm still not convinced. From what I can see, the single-element `std:initializer_list` will resolve to an *actual* `T` object ... and there is no constructor of `std:vector` that takes a single `T`. So the argument is considered as the `size_t count`. – Adrian Mole Apr 15 '23 at 11:01
  • ... adding the nested braces makes that single element *itself* an initializer-list, and there is a separate paragraph in the Standard that covers that case. – Adrian Mole Apr 15 '23 at 11:02
  • Also, `C` in the OP's code is not an aggregate class, because it has a user-defined constructor. – Adrian Mole Apr 15 '23 at 11:05
  • if I use `using T = std::vector` instead of `using T = std::vector`, the program prints `5, ` for both compilers (MSVC and G++). – Matvei Apr 15 '23 at 11:10
  • Maybe [this paragraph](https://eel.is/c++draft/over.ics.list#7.1) is the pertinent one? – Adrian Mole Apr 15 '23 at 11:11
  • Should we add a `language-lawyer` tag to this question? – Adrian Mole Apr 15 '23 at 11:14