49

I do not understand why initializer lists cannot be used on the RHS of an operator. Consider:

class foo { };

struct bar
{
    template<typename... T>
    bar(T const&...) { }
};

foo& operator<<(foo& f, bar const&) { return f; }

int main()
{
    foo baz;
    baz << {1, -2, "foo", 4, 5};

    return 0;
}

The latest Clang (gcc as well) complains:

clang.cc:14:9: error: initializer list cannot be used on the right hand side of operator '<<'
    baz << {1, -2, "foo", 4, 5};
    ^  ~~~~~~~~~~~~~~~~~~~~

    ^  ~~~~~~~~~~~~~~~

Why would the C++ standard forbid this? Or put differently, why does this fail as opposed to

baz << bar{1, -2, "foo", 4, 5};

?

mavam
  • 12,242
  • 10
  • 53
  • 87
  • 9
    Because you haven't overloaded `operator<<` to take an `initializer_list<>` on the RHS... What is your actual question? – ildjarn Jul 10 '12 at 19:28
  • 1
    I was hoping that this is equivalent to `baz << bar{1, 2, 3, 4, 5};`, but it seems there is no conversion occurring. – mavam Jul 10 '12 at 19:34
  • 3
    If that's the behavior you want, maybe you should try giving `bar` a non-explicit constructor that takes a single `initializer_list<>`. – ildjarn Jul 10 '12 at 19:35
  • 1
    This cannot work because an `initializer_list` has *exactly* one template parameter, as opposed to the variadic constructor having multiple argument types. – mavam Jul 10 '12 at 19:44
  • 3
    It's funny how this: `operator<<(baz, {1, -2, "foo", 4, 5});` does work. – mfontanini Jul 10 '12 at 20:23
  • @mfontanini: You are getting to the root of my question... – mavam Jul 10 '12 at 21:21
  • Here is a discussion on the subject, feel free to express an opinon https://groups.google.com/a/isocpp.org/d/msg/std-proposals/nXjimf0amus/nd-ilyXnFwAJ – user362515 Sep 19 '16 at 17:41

1 Answers1

65

Indeed the final version of C++11 does not enable the use of initializer lists on the right-hand side (or left-hand side, for that matter) of a binary operator.

Firstly, initializer-lists are not expressions as defined in §5 of the Standard. The arguments of functions, as well as of binary operators, generally have to be expressions, and the grammar for expressions defined in §5 does not include the syntax for brace-init-lists (i.e. pure initializer-lists; note that a typename followed by a brace-init-list, such as bar {2,5,"hello",7} is an expression, though).

In order to be able to use pure initializer-lists conveniently, the standard defines various exceptions, which are summarized in the following (non-normative) note:

§8.5.4/1 [...] Note: List-initialization can be used
— as the initializer in a variable definition (8.5)
— as the initializer in a new expression (5.3.4)
— in a return statement (6.6.3)
— as a function argument (5.2.2)
— as a subscript (5.2.1)
— as an argument to a constructor invocation (8.5, 5.2.3)
— as an initializer for a non-static data member (9.2)
— in a mem-initializer (12.6.2)
— on the right-hand side of an assignment (5.17)
[...]

The fourth item above explicitly allows pure initializer-lists as function arguments (which is why operator<<(baz, {1, -2, "foo", 4, 5}); works), the fifth one allows it in subscript expressions (i.e. as argument of operator[], e.g. mymap[{2,5,"hello"}] is legal), and the last item allows them on the right-hand side of assignments (but not general binary operators).

There is no such exception for binary operators like +, * or <<, hence you can't put a pure initializer list (i.e. one that is not preceded with a typename) on either side of them.

As to the reasons for this, a draft/discussion paper N2215 by Stroustrup and Dos Reis from 2007 provides a lot of insight into many of the issues with initializer-lists in various contexts. Specifically, there is a section on binary operators (section 6.2):

Consider more general uses of initializer lists. For example:

v = v+{3,4};
v = {6,7}+v;

When we consider operators as syntactic sugar for functions, we naturally consider the above equivalent to

v = operator+(v,{3,4});
v = operator+({6,7},v);

It is therefore natural to extend the use of initializer lists to expressions. There are many uses where initializer lists combined with operators is a “natural” notation.
However, it is not trivial to write a LR(1) grammar that allows arbitrary use of initializer lists. A block also starts with a { so allowing an initializer list as the first (leftmost) entity of an expression would lead to chaos in the grammar.
It is trivial to allow initializer lists as the right-hand operand of binary operators, in subscripts, and similar isolated parts of the grammar. The real problem is to allow ;a={1,2}+b; as an assignment-statement without also allowing ;{1,2}+b;. We suspect that allowing initializer lists as right-hand, but nor [sic] as left-hand arguments to most operators is too much of a kludge, [...]

In other words, initializer-lists are not enabled on the right-hand side because they are not enabled on the left-hand side, and they are not enabled on the left-hand side because that would have posed too big a challenge for parsers.

I wonder if the problem could have been simplified by picking a different symbol instead of curly braces for the initializer-list syntax.

jogojapan
  • 68,383
  • 11
  • 101
  • 131
  • 3
    A different symbol might have made more things possible, but `{}` is such a natural extension of the array initializers and POD struct initializers inherited from C89. – aschepler Jul 13 '12 at 01:15
  • 3
    Thanks for this explanation - I searched for tenary operator `true?{1,2,3}:{4,5,6}` - so this is not only binary operators problem... – PiotrNycz Jun 13 '14 at 15:40
  • 1
    @PiotrNycz That may not be a parsing issue, but a type deduction issue. Both alternatives of the ternary operator need to agree on a common type, and something also has to determine the result value category. The braces obscure the meaning of the program. It's better in `?:` to be explicit and put the type-name before the `{`, and this doesn't sacrifice any expressive power. – Potatoswatter Jul 12 '14 at 00:32
  • 2
    @jogojapan Do you think this reasoning - to forbid RHS because we cant have both is justified? Isn't that a case "the perfect in the way of the good?" After all, it is powerful feature still useful RHS-only. For instance, for the OP example RHS is enough, swapping the args will not even make sense! – user362515 Sep 19 '16 at 17:54
  • 1
    @user362515 Yes, you might be right. Adding a few more exceptions for RHS (or perhaps even enabling it on the RHS of every binary operator) might have been a better compromise. – jogojapan Sep 20 '16 at 00:12
  • 3
    @jogojapan Yeah, I don't think RHS only is "a kludge" - deduction is vastly more often needed there. I have started a discussion in the hope the draconian rules to be revisited https://groups.google.com/a/isocpp.org/d/msg/std-proposals/nXjimf0amus/nd-ilyXnFwAJ – user362515 Sep 20 '16 at 08:05