I just finished watching a talk from CppCon 2014 by Scott Meyers: Type Deduction and Why You Care. All in all it was a very interesting and entertaining talk, and I learned a thing or two, especially about the combination of type deduction and braced initializers. Since this blog is about simplifying the use of C++, I want to have a short look at that special combination and derive a rule of thumb from it.

A Short Summary of the Topic

The part of the talk that I am referring to in this post starts at about 29:20. After having talked about template type deduction and type deduction for `auto` variables for the more usual cases, Scott explains how type deduction works together with braced initializers. (If you just have watched the video, you can just jump to the next section).

The key point is that braced initializers like `{ 1, 42, 5 }` and `{ 3.14 }` don’t have a type. Therefore template type deduction does not work when they are passed to a function template:

template <class T> void foo(T t); int main() { foo( { 1, 2, 3, 5, 8 } ); //ERROR foo( { 0xBAD } ); //ERROR }

However, there is a special rule in C++11/14, that `auto` variables which are initialized with such a braced initializer are deduced to be of type `std::initializer_list<X>`, where X is the type of the elements of the initializer. This rule applies regardless of the number of elements and of whether copy initialization (i.e. with `=`) or direct initialization (without `=`) is used:

//C++14 auto a = { 1, 2, 3 }; //initializer_list<int> auto b { 42 }; //ditto

Then there is a proposal for C++17, N3922, which wants to change the rules: They remain the same for copy initialization, but direct initialization is only allowed with braced initializers that contain only a single element, and the variable then shall have the type of that element:

//N3922 auto a = { 1, 2, 3 }; //std::initializer_list<int>, as before auto b { 42 }; //NEW: int auto c { 42, 7 }; //NEW: compile error

The proposal has been adopted into the working draft for C++17, and at least one compiler (Microsoft Visual Studio) already implements that rule. Scott has also written a blog post on this issue.

What to Make of This

As of today, there is only one single sane way I can think of to deal with the mess in this little corner of the language:

Don’t use braced initializers together with type deduction.

Am I simplifying too much? I don’t think so, and here is why:

It Does Not Always Work Anyways

As written above, braced initializers don’t work at all with template type deduction. The same applies for C++11 lambda captures and `decltype`. What remains is `auto` type deduction and C++14’s init-capture for lambdas, which uses the same set of rules. So the next points are about `auto` type deduction:

It’s Not Clear

The syntactic difference between copy initialization and direct initialization is too small for such a huge semantic difference. Anybody reading a line where `auto` is used together with a braced initializer will have to know the rules. For direct initialization she will have to know both rules and which one of them apply to the compiler that is used to compile the code or deduce from the rest of the code which rule might be meant. It’s a maintainability nightmare.

It’s not Portable

Different compilers implement this differently. And not only switching to another compiler might break code that uses direct initialization with braced initializers, upgrading between versions of the same compiler might suffice if the proposal gets accepted into C++17, or in the case of MSVC, if it does not get accepted and the current behavior is removed from the compiler.

It’s a Corner Case

There is little or no use for `std::initializer_list` as standalone variables, at least as far as I know of (I would love to hear from sensible real world examples in the comments). If there are occasions where a variable of the type is needed, the programmer should explicitly use the type instead of `auto`.

“Wait, what?? No `auto`?” Not this time. `auto` is great for standard cases where the exact type of a variable is not necessary to know or where it is easy to derive from the context. In this case, where you want to use a not so usual type, document it. If you insist on using auto, initialize your `auto` variable with an explicitly constructed `std::initializer_list`:

auto il = std::initializer_list<int>{ 1, 2, 3 };

That way every reader of the code will know that you indeed meant to use an `initializer_list` and did not just happen to fall into a trap that type deduction rules made for you.

Scott Meyers has written a new blog post, sheding some light on the rationale behind the auto deduction rules for braced initialitzers.