Skip to content

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?

Answer

No.

a has 5 element(s): 2 2 2 2 2
b has 2 element(s): 5 2

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

Open in Compiler Explorer ↗ Quiz this entry