Why is comma operator called inside operator [] but not inside operator ()?
Asked Answered
H

2

8

From this former question When all does comma operator not act as a comma operator?, I understood that commas inside a function call can only act as expression sperator. But from the code below, it appears that operator() behaves like a function call while operator[] does not.

So I have two questions :

  1. Why is the comma operator called inside an operator[] call and not inside an operator() call ?
  2. Is there a specific reason that prevents the compiler, first checking that f(a,b) does not match both the arity or the types of any f declaration, would not try to change the comma status and see if f(a.operator,(b)) leads to an acceptable synthax ? From my point of view, it would be the same kind of process that comes with types conversion.

Code example :

struct A{ };

struct B {
  A operator,(const B & other) const { return A(); }
};

struct C {
  C(){}
  C(const A & a){}
  void operator[](const A & a) const {}
  void operator()(const A & a) const {}
};

void f(const A & a){}

int main()
{
    B x,y;
    C z;

    //these do no compile because ',' in a function call is an argument separator
    //C(x,y);
    //f(x,y);

    //but this one compiles as z[x.operator,(y)]
    z[x,y];

    //and this one does not
    //z(x,y);

    //finally all of these do compile
    z((x,y));
    C((x,y));
    f((x,y));

   return 0;
}
Herzberg answered 19/12, 2017 at 13:38 Comment(12)
Look where "expressions" can be used in the syntax. A parameter pack in not an expression. A array index is an expression. The comma operator can only be used in expressions. Putting round brackets around stuff can in some cases turn them into expressions. For a full answer add the language-lawyer tag and/or read the standard.Hedjaz
Well the operator() is called the "function call operator" as it is used (and parsed) as that.Bi
That would not make the code more readable, don't you think?Vanbuskirk
It would be incredibly confusing if the existence of an operator, could decide whether z(x,y) had one or two arguments.Oahu
@molbdnilo: that would be an understatement. You wouldn't even know which f to consider in the presence of overloads. And what to think about f(x,y,z) when f takes two parameters - do you try both f(x,(y,z)) and f((x,y),z) ?Copley
@RichardCritten : Thank you for the tag suggestion. I did not expect the overloaded operator[] to keep the same array index semantic and so to require an expression.Herzberg
@Oahu I agree it would be confusing, I am just asking out of curiosity. But for example my ide is highlighting custom operators so z(a,b) with one or two arguments would be visually different.Herzberg
@Copley : So the answer is that it would lead to combinatorial explosion in the general case, and such not feasible ?Herzberg
@Herzberg concider the function call: f(a, b, c, d, e ,f); which is the comma-operator and which is the parameter-list-separator? Too hard for the compiler and to be backwards compatible they are all parameter-list-separator(s). Knowing that now concider: g((a, b), (c), (d, e, f)); You should see that f takes 6 parameters and g takes only 3. With the extra commas (inside the extra brackets) in g being operators (the brackets around c are optional).Hedjaz
@Herzberg A language must be understandable without concern for your preferred IDEs text-rendering habits.Oahu
@yultan. Correct. Even the simplest overload pair f(int) and f(int,int) would need special rules to prevent f(1,2) form being ambiguous.Copley
Note that since C++23 operator[] will have same properties as function since multidimensional subscript has been introduced.Needleful
P
9

Why is the comma operator called inside an operator[] call and not inside an operator() call?

If you look grammatically, function calls are of the form postfix-expression ( expression-listopt ). An expression-list (which is an initializer-list, not to be confused with std::initializer_list) is a comma separated list of initializer-clauses (assuming there are at least two clauses). The commas are consumed by the parsing of the expression-list, where it has special meaning, rather than an part of an expression.

Indexing is of the form postfix-expression [ expr-or-braced-init-list ], there is no comma to be consumed at this point, so any comma that appears is necessarily part of the expression.

Is there a specific reason that prevents the compiler, first checking that f(a,b) does not match both the arity or the types of any f declaration, would not try to change the comma status and see if f(a.operator,(b)) leads to an acceptable syntax ?

I'm going to go with "sanity." Function calls are a really fundamental aspect of programs and they need to be straightforward. It would be insanely error-prone if you couldn't even know how many arguments you were passing. Especially if the builtin comma operator is used, which simply ignores arguments.

Moreover, it's very simple to force the use of a comma: add parentheses:

f(a, (t=3, t+2), c);

has three arguments, the second of which has value 5.

This works grammatically because the interior comma can't be a comma separating initializer-clauses, since (t=3 is not an initializer-clause.

Pod answered 19/12, 2017 at 14:45 Comment(1)
Besides the general insanity of the idea, you don't know which fs you are looking at until you know the arguments (because of ADL), and if you don't know what the arguments are until you know which fs exist...Deafening
G
0

The question and answer explain the behavior of commas in [] subscripts correctly for the time they were written.

However, for readers in the present and future:

With C++20 the use of , directly in [] was deprecated. It should now be parenthesized, meaning that instead of z[x,y] one should use z[(x,y)]. The comma is still interpreted as operator, (or the built-in comma operator) in either case though.

With C++23 the meaning of the comma in z[x,y] (without parentheses) will change. It will now be interpreted as operator[](x,y), not operator[](operator,(x,y)), just as z(x,y) has always been interpreted as operator()(x,y). Previously operator[] could only have one argument. With C++23 it will be allowed to have multiple arguments behaving the same way as operator().

This is a breaking change in the language, although use of the comma operator in a [] subscript had almost no practical uses and probably surprising/unintended behavior given the difference to () and the seemingly multi-dimensional indexing nature of the syntax.

Glazed answered 9/6, 2022 at 13:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.