37 Intermediate The Hundred
Braces Change the Constructor
the harmless-looking swap from ( to {
#include <iostream>
#include <vector>
void print(const char* name, const std::vector<int>& v) {
std::cout << name << " has " << v.size() << " element(s):";
for (int x : v)
std::cout << ' ' << x;
std::cout << '\n';
}
int main() {
std::vector<int> a(5, 2); // five elements, each equal to 2
std::vector<int> b{5, 2}; // same thing, modern syntax... right?
print("a", a);
print("b", b);
}
Run it. Do a and b print the same thing?
Why¶
When a class has a std::initializer_list constructor, list-initialization is greedy:
the compiler tries the initializer_list constructors first, and looks at the others
only if none of those is viable. {5, 2} is two ints — a perfect
initializer_list<int> — so vector's "here are the elements" constructor hijacks the
call, even though (5, 2) matches the "count, value" constructor exactly. That rule is
deliberate, so that {1, 2, 3} always means those three elements — but it also means
mechanically "modernizing" parentheses into braces can silently change what a line does.
One carve-out: empty braces, as in std::vector<int> v{};, call the default constructor,
not an empty-list one. -Wall -Wextra says nothing here — both lines are perfectly
well-formed; they just build different vectors.
The fix¶
There is nothing to fix in the language — pick the syntax that says what you mean:
std::vector<int> a(5, 2); // count + value: 2 2 2 2 2
std::vector<int> b{5, 2}; // literal elements: 5 2
Takeaway: braces are not a drop-in replacement for parentheses — on a type with an
initializer_list constructor, () and {} are two different APIs.
Try it: g++ -std=c++17 main.cpp -o demo && ./demo