28

Why this code is not valid?

auto foo = [] () {
    return {1, 2};     
};

However, this is valid since the initializer_list is used just to initialize a vector not to return itself:

auto foo = [] () -> std::vector<int> {
    return {1, 2};     
};

Why I can not return initializer_list? It could be useful. For example, a lambda that can be used to initialize a vector or a list or ... with some default values for something.

wtz
  • 426
  • 4
  • 15
Humam Helfawi
  • 19,566
  • 15
  • 85
  • 160

3 Answers3

32

Lambda return type deduction uses the auto rules, which normally would have deduced std::initializer_list just fine. However, the language designers banned deduction from a braced initializer list in a return statement ([dcl.spec.auto]/7):

If the deduction is for a return statement and the initializer is a braced-init-list ([dcl.init.list]), the program is ill-formed.

The reason for this is that std::initializer_list has reference semantics ([dcl.init.list]/6).
[]() -> std::initializer_list<int> { return {1, 2}; } is every bit as bad as
[]() -> const int & { return 1; }. The lifetime of the backing array of the initializer_list object ends when the lambda returns, and you are left with a dangling pointer (or two).

Demo:

#include <vector>

struct Noisy {
    Noisy()  { __builtin_printf("%s\n", __PRETTY_FUNCTION__); }
    Noisy(const Noisy&) { __builtin_printf("%s\n", __PRETTY_FUNCTION__); }
    ~Noisy() { __builtin_printf("%s\n", __PRETTY_FUNCTION__); }
};

int main()
{
    auto foo = []() -> std::initializer_list<Noisy> { return {Noisy{}, Noisy{}}; };
    std::vector<Noisy> bar{foo()};
}

Output:

Noisy::Noisy()
Noisy::Noisy()
Noisy::~Noisy()
Noisy::~Noisy()
Noisy::Noisy(const Noisy&)
Noisy::Noisy(const Noisy&)
Noisy::~Noisy()
Noisy::~Noisy()

Note how the copy constructors are called after all the Noisy objects created so far have been destroyed already.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • 1
    So it is simply not valid to do this at all.. Thanks sir for explanation. – Humam Helfawi Feb 12 '16 at 11:46
  • 2
    @HumamHelfawi Your `vector` version works fine, but is not ideal. I'd consider returning an `std::array` which seems to be close to what you want, except you have to manually count the number of objects. Maybe you can get something done with `constexpr` to automatically extract the array size. – nwp Feb 12 '16 at 11:57
7

std::initializer_list can't be deduced by a template argument, which means you'll have to tell the lambda what it is explicitly:

#include <initializer_list>
#include <iostream>
#include <vector>

int main()
{
    auto foo = []() -> std::initializer_list<int> { return {1, 2}; };
    std::vector<int> bar{foo()};
    for (int x : bar) { std::cout << x << "  "; };
}

Demo. Here's the rationale behind this from the initializer list proposal:

Can an initializer list be used as a template argument? Consider:

template<class T> void f(const T&);
f({ }); // error
f({1});
f({1,2,3,4,5,6});
f({1,2.0}); // error
f(X{1,2.0}); // ok: T is X

There is obviously no problem with the last call (provided X{1,2.0} itself is valid)
because the template argument is an X. Since we are not introducing arbitrary lists of
types (product types), we cannot deduce T to be {int,double} for f({1,2.0}), so that call is
an error. Plain {} does not have a type, so f({}) is also an error.

This leaves the homogeneous lists. Should f({1}) and f({1,2,3,4,5,6}) be accepted? If so,
with what meaning? If so, the answer must be that the deduced type, T, is
initializer_list. Unless someone comes up with at least one good use of this simple
feature (a homogeneous list of elements of type E is deduced to be an
initializer_list), we won’t propose it and all the examples will be errors: No template
argument can be deduced from an (unqualified) initializer list. One reason to be cautious
here is that we can imagine someone getting confused about the possible interpretations
of single-element lists. For example, could f({1}) invoke f<int>(1)? No, that would be
quite inconsistent.

Brian Rodriguez
  • 4,250
  • 1
  • 16
  • 37
  • 1
    Thanks. But so strange.. it's just a problem of auto deduced! Are you sure that it is totaly valid and no UB or any other problem could be happen here? becuase i have doubt that it is valid but just a deduction problem.. – Humam Helfawi Feb 12 '16 at 11:31
  • 2
    @HumamHelfawi Yes, the standard explicitly says `std::initializer_list` can't be type-deduced. Here's an [answer](http://stackoverflow.com/questions/12431495/initializer-list-and-template-type-deduction) that goes into more details. – Brian Rodriguez Feb 12 '16 at 11:38
  • 3
    Do not confuse `std::initializer_list` with a braced init list – Piotr Skotnicki Feb 12 '16 at 11:39
  • 2
    @HumamHelfawi Found the rationale from the proposal, should make things clearer for you. – Brian Rodriguez Feb 12 '16 at 11:59
1

You can return an initializer_list from the function either like that:

return std::initializer_list<int>{1, 2};

or

auto ret = {1, 2};
return ret;

The reason is, that the auto variable declaration uses different rules than the auto return type deduction. The first one has a special rule for this case, and the second one uses plain template type deduction.

This is discussed at length in Scott Meyers Effective Modern C++, Item 2. There is also a video and slides from him about the topic.

Zulan
  • 21,896
  • 6
  • 49
  • 109