Auto and braced-init-lists

ISO/IEC JTC1 SC22 WG21 N3681 - 2013-05-02

Ville Voutilainen, ville.voutilainen@gmail.com

Abstract

Auto and braced initializers cause a teachability problem; we want to teach people to use uniform initialization, but we need to specifically tell programmers to avoid braces with auto. In C++14, we now have more cases where auto and braces are problematic; return type deduction for functions partially avoids the problem, since returning a braced-list won't work as it's not an expression. However, returning an auto variable initialized from a braced initializer still returns an initializer_list, inviting undefined behaviour. Lambda init captures have the same problem. This paper proposes to change a brace-initialized auto to not deduce to an initializer list, and to ban brace-initialized auto for cases where the braced-initializer has more than one element.

Introduction

The crux of the problem is the following:


auto x = foo(); // copy-initialization
auto x{foo}; // direct-initialization, initializes an initializer_list
int x = foo(); // copy-initialization
int x{foo}; // direct-initialization

The difference between auto and a normal type is unfortunate, counter-intuitive and surprising. The same issue apparently arises with an init-capture:

[x = foo()](){} // copy-initialization
[x{foo()}](){} // direct-initialization, initializes an initializer_list.

Function return type deduction partially avoids the problem:

auto f()
{
    return {1,2}; // ill-formed, {1,2} is not an expression
}

But the situation arises again with an auto variable:

auto f()
{
    auto x{1,2}; // direct-initialization, initializes an initializer_list.
    return x; // returns an initializer_list, instant UB to access it
}

We shouldn't have such bear-traps in the language. The init-capture results in object lifetime issues, and so does the return case.

Proposed solution

Change in 7.1.6.4 auto specifier [dcl.spec.auto] paragraph 6:

Let T be the type that has been determined for a variable identifier d. Obtain P from T by replacing the occurrences of auto with either a new invented type template parameter U or, if the initializer is a braced-init-list (8.5.4), with std::initializer_list<U>a new invented type template parameter U that is the declared type of the expression in the braced-init-list. If the braced-init-list has more than one element, the program is ill-formed. The type deduced for the variable d is then the deduced A determined using the rules of template argument deduction from a function call (14.8.2.1), where P is a function template parameter type and the initializer for d is the corresponding argument. If the deduction fails, the declaration is ill-formed. [ Example:

auto x1 = { 1, 2 }; // decltype(x1) is std::initializer_list<int>
auto x2 = { 1, 2.0 }; // error: cannot deduce element type

— end example ]


auto x1 = { 1, 2 }; // error: cannot have multiple elements in a braced-init-list with auto


— end example ]

[ Example:

const auto &i = expr;

const auto &i{expr};

The type of i is the deduced type of the parameter u in the call f(expr) of the following invented
function template:

template <class U> void f(const U& u);

— end example ]