74 Advanced The Hundred
One Brace, Two Meanings
same braces, same 42, one stray =
#include <initializer_list>
#include <iostream>
#include <type_traits>
int main() {
auto a{42}; // braces are the modern way to initialize, right?
auto b = {42}; // same thing, spelled with an equals sign
std::cout << "sizeof(a) = " << sizeof(a) << "\n";
std::cout << "sizeof(b) = " << sizeof(b) << "\n";
// compile-time proof of what was deduced:
static_assert(std::is_same_v<decltype(a), int>);
static_assert(std::is_same_v<decltype(b), std::initializer_list<int>>);
}
Run it. Which of the two static_asserts fails — and what two sizes does it print?
Answer
Neither: both hold. That one = is the whole difference, and the program prints:
Why¶
A braced list has no type of its own, so auto needed a special deduction rule — and it
got two. The direct form auto a{42} unwraps the single element and deduces int; the
copy form auto b = {42} wraps it and deduces std::initializer_list<int>, a
pointer-plus-length view of a hidden temporary array — hence 16 bytes on a 64-bit target.
-Wall -Wextra has nothing to say about any of it. The direct form is also strict:
exactly one element, so -DSHOW_BUG adds auto c{1, 2}; and GCC 11 refuses — while
cheerfully naming the = as the switch:
error: direct-list-initialization of ‘auto’ requires exactly one element
note: for deduction to ‘std::initializer_list’, use copy-list-initialization
(i.e. add ‘=’ before the ‘{’)
Before N3922 (C++17, applied
retroactively — GCC 11 deduces this way even in -std=c++11) both forms deduced
initializer_list.
The fix¶
auto a = 42; // int — no braces, no doubt
std::initializer_list<int> b{42}; // want a list? say the type
Takeaway: never combine auto with braces — unless you truly want an
initializer_list, and then write the type out.
Try it: g++ -std=c++17 main.cpp -o demo && ./demo — then again with -DSHOW_BUG