3

Why is the following code illegal?

for (int index=0; index<3; index++)
{
    cout << {123, 456, 789}[index];
}

While this works fine:

for (int value : {123, 456, 789})
{
    cout << value;
}

Code in IDEOne: http://ideone.com/tElw1w

StilesCrisis
  • 15,972
  • 4
  • 39
  • 62
  • 3
    [`std::initializer_list`](http://en.cppreference.com/w/cpp/utility/initializer_list) has members `begin` and `end`, but no `operator[]` – clcto Sep 15 '15 at 22:57
  • As T.C. already mentioned, `{123, 456, 789}` is not a type and also no expression - to go a bit further `decltype({1, 2, 3})` is defined being ill-formed. An exception is the type deduction when using `auto`. – HelloWorld Sep 15 '15 at 23:07

2 Answers2

5

While std::initializer_list does not provide an operator[], it does have overloads for begin() and end() which are what the range based for uses. You can in fact index into an initializer_list like this:

    for (int index=0; index<3; index++)
    {
        cout << begin({123, 456, 789})[index];
    }
mattnewport
  • 13,728
  • 2
  • 35
  • 39
  • Cool trick! And makes more sense than "some cases are special snowflakes." – StilesCrisis Sep 15 '15 at 23:14
  • 1
    Note the cost: one temporary array creation/destruction per iteration. For non-trivial types, this gets expensive quite fast. – T.C. Sep 15 '15 at 23:36
  • It has to copy the ints from the `.data` section to the stack? Why? (Or worse, is it constructing a `vector` or something?) – StilesCrisis Sep 15 '15 at 23:42
  • @T.C. good point, I wasn't advocating doing this, just illustrating it is possible though. – mattnewport Sep 15 '15 at 23:43
  • 1
    @StilesCrisis the standards says "An object of type std::initializer_list is constructed from an initializer list as if the implementation allocated a temporary array of N elements of type const E, where N is the number of elements in the initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer list, and the std::initializer_list object is constructed to refer to that array." – mattnewport Sep 15 '15 at 23:48
  • 1
    Note the "as if" part. It's up to the compiler. – Karoly Horvath Sep 16 '15 at 00:47
  • It'd be cool to see if a reasonable optimizer knows how to optimize all the copies away. – StilesCrisis Sep 16 '15 at 00:59
  • @StilesCrisis For `int`s, you probably won't see copying. For more complex types, it may not be able to optimize it away (ctor/dtor might have side effects). Also, the pointer returned by `begin({...})` is only valid until the end of the full expression, so it's easy to use a dangling pointer by mistake. – T.C. Sep 16 '15 at 09:53
  • Well, if you use a reference, couldn't you extend the lifetime of the object? – StilesCrisis Sep 16 '15 at 15:41
2

A braced-init-list like {123, 456, 789} has no type by itself, and cannot be indexed (nor indeed used with most other operators).

The range-based for loop has special handling for this case to make it work. (Technically, the special handling is in the auto&& it uses internally, which deduces a std::initializer_list from a braced-init-list.)

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • So is there a list of contexts where a braced-init-list is legal? – StilesCrisis Sep 15 '15 at 23:07
  • @StilesCrisis There's a long note that summarizes it in [dcl.init.list]/p1. [This answer](http://stackoverflow.com/a/28461182/2756719) has a quote. – T.C. Sep 16 '15 at 09:47