Initializer Lists and Rhs of Operators

Initializer lists and RHS of operators

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.

Why can I use initializer lists on the right-hand side of operator += but not operator+?

It is explained in the answer to this question (which is linked from the question you linked to).

The language grammar only allows a braced list in certain grammatical contexts, not in place of an arbitrary expression. That list includes the right-hand side of assignment operators, but NOT the right-hand side of operators in general.

+= is an assignment operator, + is not.

The grammar for assignment expressions is:


assignment-expression:
conditional-expression
logical-or-expression assignment-operator initializer-clause
throw-expression
assignment-operator: one of
= *= *= /= %= += -= >>= <<= &= ^= |=

std::initializer_list as right hand argument for overloaded operator?

You need to take the initializer_list by const&:

bool operator==(const int lhs, const std::initializer_list<int>& il)
std::cout << (3 == std::initializer_list{1,2,3,4,5}) << '\n';

For the is_in test you could overload the comma operator and do something like this:

template<class T>
struct is_in {
is_in(const std::initializer_list<T>& il) : ref(il) {}
const std::initializer_list<T>& ref;
};

template<class T>
bool operator,(const T& lhs, const is_in<T>& rhs) {
return std::find(rhs.ref.begin(), rhs.ref.end(), lhs) != rhs.ref.end();
}

int main() {
bool b = (5, is_in{ 1, 2, 3, 4, 5 });

std::cout << b << '\n';
}

Can operators be overloaded for initializer_list literals?

As far as I understand 13.5/6,

An operator function shall either be a non-static member function or be a non-member function and have
at least one parameter whose type is a class, a reference to a class, an enumeration, or a reference to an
enumeration

this should not be possible. Braced-init-lists are not the same as std::initializer_lists, and they're not interchangeable. The following should work, though:

std::initializer_list<int>({1,2}) + std::initializer_list<int>({2,1})

Or this:

operator+({1,2}, {2,1})

Update: To clarify, the point is that there's no rule in the language grammar that allows a braced-init-list to appear in the context suggested by the OP. Braced-init-lists can only appear in contexts where some­thing is about to be initialized, if you will.

Overloading != operator does not work if second argument is a rvalue

The compiler tells you. For instance GCC says:

$ g++ -std=c++17 source.cpp && ./a.out
source.cpp: In function ‘int main()’:
source.cpp:24:12: error: expected primary-expression before ‘{’ token
24 | if(ob!={211,121}){
| ^
source.cpp:24:12: error: expected ‘)’ before ‘{’ token
24 | if(ob!={211,121}){
| ~ ^
| )

whereas Clangd (I use it in the IDE) goes straight to the point:

Initializer list cannot be used on the right hand side of operator '!=' [init_list_bin_op]

so you simply can't do something != {init list}. Notice that this is true in much simpler cases:

int x = {}; // ok
x != {}; // not ok

Notice that this has no relation with the rhs of != being an rvalue. Indeed ob != MyClass{211,121} works, and MyClass{211,121} is an rvalue.

As regards to why ob.operator!=({211,121}) works fine, it's because it's a function call to operator!= member function, which is known to take a const MyClass&, which the {211,121} can be converted to.

Concerning why {init-list} is forbidden after operators, it is throughly explained here.

Initializer lists for multidimensional arrays and vectors

Use one more pair of braces.

auto data4 = std::array<std::vector<int>, 3> { { {0,0,0}, {1,1,1}, {2,2,2} } };

Otherwise the first list {0,0,0} is considered as an initializer of the whole object of the type std::array<std::vector<int>, 3 >..

std::array is an aggregate. From the C++ 14 Standard (23.3.2.1 Class template array overview)

2 An array is an aggregate (8.5.1) that can be initialized with the
syntax

array<T, N> a = { initializer-list }; 

where initializer-list is a comma-separated list of up to N elements
whose types are convertible to T.

Overload type conversion to initializer list

Unfortunately, v + {1, 2} is not a well-formed expression in C++ for grammatical rules. Most binary operators only take expressions for each of the operands, and the braced list is not an expression. The compound assignment operator += is special, because assignment also accepts an initializer-clause for the right-hand operand, so v += {1, 2} happens to work.

Some alternatives:

operator+(v, {1, 2})
v + Vec<int>({1, 2})


Related Topics



Leave a reply



Submit