Passing multiple arguments to operator[] - c++

While I didn't know you cannot overload operator[] in C++ to accept more than one argument, I have accidentally came across the statement that appeared to be valid to my surprise:
#include <vector>
#include <iostream>
int main()
{
std::vector<int> numbers{1, 2, 3, 4};
int i = 0;
std::cout << numbers[i++,i+=1,i=1,i+1] << std::endl;
return 0;
}
So could anyone please explain if there is any benefit of passing multiple expressions to operator[] ?
compiled with mingw g++ 4.8.1 with -std=c++11

You are not passing multiple arguments to an overloaded operator, but instead you use the comma operator for evaluating a single function parameter. There is no benefit associated with it, except confusing co-workers you dislike. The statement
numbers[i++,i+=1,i=1,i+1]
evaluates i++, then i += 1, then i = 1, then i + 1 and returns the last evaluated expression, which is 2.
While there are valid use cases for the comma operator, this is not one of them.

In C++, comma works both as a separator and an operator. In this particular case, it works as an operator.
In a comma expression E1, E2, the expression E1 is evaluated, its result is discarded (although if it has class type, it won't be destroyed until the end of the containing full expression), and its side effects are completed before evaluation of the expression E2 begins (note that a user-defined operator, cannot guarantee sequencing) (until C++17).
The type, value, and value category of the result of the comma expression are exactly the type, value, and value category of the second operand, E2. If E2 is a temporary expression (since C++17), the result of the expression is that temporary expression (since C++17). If E2 is a bit-field, the result is a bit-field.
So
numbers[i++,i+=1,i=1,i+1];
becomes
numbers[i+1];

Apart from possible side effects, there isn't any benefit, as your numbers[i++, i+=1, i=1, i+1] expression evaluates to numbers[i+1] which is equal to numbers[2] because of how things work when using the comma operator. We can argue this is confusing, hard to read and is of no benefit.

This is something that might work in interpreted languages like Python, but not in C++.
In this case you have to chain them with the numbers in front
...<<numbers[i-1]<<number[i]<<...
Try to base your design around this limitation.

Related

Does each expression in C++ have a non-reference type

Hi i am reading about expression in C++ and across the statement
Statement 0.0
Each expression has some non-reference type
The quoted statement is from en.cppreference.com/w/cpp/language/value_category. Check line 2 at the top of the page.
Now i took some examples to understand what this means. For example:
int i = 100; // this expression has type int
int &j = i; // this expression has type int or int&?
My confusion is that i know that j is a reference to int that is j is int& but according to the quoted statement every expression has a non-reference type will imply that int &j = i; has type int. Is this correct?
Other examples that i am getting confused about:
int a[4] = {2,4,4,9};
a[3]; // will this expression be int& type or int type?
Now in the statement a[3]; i know that a is a array lvalue and so a[3] returns a lvalue reference to the last element. But getting confused about will the quoted statement 0.0 imply that this whole expression a[3]; be a int or an int& type?
Here is another example:
b[4]; // Here assume that b is an array rvalue. So will this expression has type int&& or int?
So my question is that does something similar happen for pointers also? Meaning do we have a similar statement(0.0) for pointers also?
int x = 34;
int *l = &x; // will this expression have type int* or int?
I now that here l is a pointer to int(compound type). If there is no similar statement for pointers then what is the need for this statement for references? That is why do we strip off the reference part only?
int i = 100; // this expression has type int
int &j = i; // this expression has type int or int&?
These statements are not expressions at all. These are declararions. They do contain sub expressions 100 and i, both of which have the type int. If you used the id expression j after this declaration, the type of that expression would be int.
So my question is that does something similar happen for pointers also?
No. Pointers are non-reference types, and something similar doesn't happen to expressions with pointer types.
why do we strip off the reference part only?
This is simply how the language works. It allows us treat objects and references to objects identically.
This is part of why you dont need to (nor can you) explicitly use an indirection operator to access the referred object, unlike needing to use an indirection operator to access a pointed object.
Here is the actual language rule (from latest standard draft):
[expr.type]
If an expression initially has the type “reference to T” ([dcl.ref], [dcl.init.ref]), the type is adjusted to T prior to any further analysis.
The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression.
Expression : An expression is made up of one or more operands and yields a result when it is evaluated.
Note the wording " when it is evaluated ".
Examples of expressions are: the literal 4, or some variable n. Again note that the expression is not yet evaluated and so have no result. Also you can create more complicated expressions from an operator and one or more operands. For example 3 + 4*5 is an unevaluated-expression. An expression with two or more operators is called a compound expression.
Each expression in C++ is either a rvalue or a lvalue.
Expression Statement: Statements end with a semicolon. When we add a semicolon to an expression, it becomes an expression statement. The affect of this is that this causes the expression to be evaluated and its result discarded at the end of the statement. So for example the literal 5 is an expression but if you add a semicolon ; after it then we will have a expression statement. Also the result of this expression will be discarded at the end of the expression statement. Lets looks at another example, cout << n; is a expression statement as a whole. It consists of the following expressions:
1. expression `cout`
2. expression 'n'
And it consists of one operator << and a null statement ;
This whole statement will cause a side-effect which will be the printing of value of n on the screen.
Update:
Example 1: std::cout << n; will have a side-effect of printing the value of n on the screen but more importantly it will also have a resulting value which will be the object std::cout which is discarded at the end of the statement.
Example 2: int i(20 + 1); consists of 3 things:
type : int
identifier i
expression 20 + 1
Example 3: float p; This has no expression. This is just variable definition. Also called declaration statement.
Example 4: float k = 43.2; This has an expression at the right hand side which is 43.2, a type float and an identifier k.
Example 5: i = 43;. This is an expression statement. There are two expressions and one operator here. The result is the variable i.
Example 6: int &r = i;. This is a declaration statement since it consists of the expression i on the right hand side. Also on the left hand side we have a type(int) and a declarator(&r). Since it is not an expression statement there will be no value that will be discarded.
Example 7: int *p = &i; This is an declaration statement since it consists of the expression i on the right hand side. Also on the left hand side we have a type(int) and a declarator(*p). Since it is not an expression statement there will be no value that will be discarded.
Example 8: i = a < b ? a : b; This is an expression statement. Here the expressions are:
the left hand side variable i
the variable a on the right hand side
the variable b on the right hand side
Also there is one condition in the middle(a < b ). If the condition evaluates to true then the result of this will be the variable a and b otherwise.
I have come to the following conclusions. To begin with, note that the type of an entity is only about what you can do with this entity, for example, addition is defined for integer types, but not for all class types.
As the question states, an expression can only have a non-reference type, i.e. can be either a fundamental type, a pointer type, a function type, ..., an array type, or a class type, but not a reference type. Which means that the available options for the type of expression listed above allow us to describe everything so that we can do everything with this expression that is possible to do with it in C++. That is, even in situations where we might expect the type to be a reference type, for example,
T a;
static_cast<T&>(a); // suspicious expression
or
T a;
T& f(int &a) { return a; }
f(a); // another suspicious expression
we cannot do anything with this expressions that we could not do with an expression of the non-reference type T and of the same value category.
Note, that we can write
int a;
static_cast<int&>(a)=3;
but the program containing following lines will be ill-formed,
int b;
static_cast<int>(b)=3; // error: lvalue required as left operand of assigment.
because the ability to stand on the left side of an assignment is determined by the value category. Thus, describing an expression with a type and a value category, as opposed to describing it with a type alone, removes the need for a separate reference type, because that property is already described by the value category alone.
C++ expressions can have forms like j+0 or (j). These expressions definitely have type int.
To simplify the grammar, the name of a variable by itself is also usable as an expression. If we didn't have this rule, in the grammar we'd have a lot of variable-or-expression constructs. But it means that the expression j has the same type as the expression j+0, namely int.
I also encountered the same question and above answers don't solve it. But I find a post written by SCOTT MEYERS which discusses the question.
In a nutshell, if you take the standard literally then it is true that an expression can have reference type but may not very useful for understanding the language.

Why use this comma in this return statement? [duplicate]

This question already has answers here:
What does the comma operator , do?
(8 answers)
C++ -- return x,y; What is the point?
(18 answers)
Closed 6 years ago.
I understand what this C++ function does, but I don't understand why the return statement is written this way:
int intDivide(int num, int denom){
return assert(denom!=0), num/denom;
}
There is only one statement here, because there is only one ; but the comma confuses me. Why not write:
int intDivide(int num, int denom){
assert(denom!=0);
return num/denom;
}
Aside from "elegance" is there something to be gained in the first version?
What exactly is that comma doing anyway? Does it break a single statement into 2 parts such that essentially the above 2 versions are identical?
Although the code didn't seem to use constexpr, C++11 constexpr functions were constrained to have only one statement which had to be a return statement. To do the non-functional assertion and return a value there would be no other option than using the comma operator. With C++14 this constraint was removed, though.
I could imagine that the function was rewritten from a macro which originally read something like this
#define INT_DIVIDE(nom,denom) (assert(denom != 0), nom/denom)
The built-in comma operator simply sequences two expressions. The result of the expression is the second operand. The two functions are, indeed, equivalent. Note, that the comma operator can be overloaded. If it is, the expressions are not sequenced and the result is whatever the overload defines.
In practice the comma operator sometimes comes in quite handy. For example, it is quite common to use the comma operator when expanding a parameter pack: in some uses each of the expansions is required to produce a value and to avoid void results messing things up, the comma operator can be used to have a value. For example:
template <typename... T>
void g(T const& arg) {
std::initializer_list<bool>{ (f(arg), true)... };
}
This is a sort of 'syntactic sugar', which is expanded on in a similar question.
Basically the e1, e2 means evaluate e1, and then evaluate e2 - and the entire statement is the result of e2. It's a short and obfuscated (in my opinion) way of writing what you suggest. Maybe the writer is cheap on code lines.
From the C++ standard:
5.19 Comma operator [expr.comma]
1 The comma operator groups left-to-right.
expression:
assignment-expression
expression , assignment-expression
A pair of expressions separated by a comma is
evaluated left-to-right; the left expression is a discarded- value
expression (Clause 5).87 Every value computation and side effect
associated with the left expression is sequenced before every value
computation and side effect associated with the right expression. The
type and value of the result are the type and value of the right
operand; the result is of the same value category as its right
operand, and is a bit-field if its right operand is a glvalue and a
bit-field. If the value of the right operand is a temporary (12.2),
the result is that temporary.
Yes, the two versions are identical, except if the comma operator is overloaded, as #StoryTeller commented.

What does (expr1, expr2) do on the right side of an assignment?

I recently looked at my old code and I can't figure out what this does, or whether it is valid. The code is something like:
map<string, string> map;
map[string1] = ("s", string2);
It's an obfuscation. The comma operator evaluates all the arguments from left to right but discards all arguments apart from the final one. The formal type of the entire expression is the type of the final argument.
(Note also that the comma itself is a sequencing point.)
string2 becomes the value in the map, under key string1.
The expression "s", string2 uses the built-in1 comma operator. It has the following semantics:
In a comma expression E1, E2, the expression E1 is evaluated, its result is discarded, and its side effects are completed before evaluation of the expression E2 begins (note that a user-defined operator, cannot guarantee sequencing) (until C++17).
The type, value, and value category of the result of the comma expression are exactly the type, value, and value category of the second operand, E2.
In case of your code snippet, it isn't useful at all, and its only purpose is to confuse readers. Since evaluating "s" has no side-effects, the code is identical to this:
map<string, string> map;
map[string1] = string2;
1 This is assuming that no user-defined operator, matching the arguments has been defined. If there is, it can do anything, and the remainder of this answer does not apply. You'd have to look into the source code to find out, what the expression evaluates to.
This is probably the comma operator, which evaluates everything then discards everything except the right hand side value.
But, if operator,( char const*, string2 ) is overloaded it could do anything (where string2 is the type of string2).
Look for such overloads.
Note that if string2 is a std::string such overloads may make your program ill formed, no diagostic required. Won't stop people from doing it. And it may "work" and still permit , to (for example) cause concatination of strings.
Comma is operator, that evaluates arguments from left to right, and has value of most-right element.
For example:
int a=2, b=3, c;
c = (a+=b, a*b);
So, from left, a+=b is evaulated first, which sets a to 2+3, then a*b is evaulated, and expression has value of 5*3, which value is used. So c is 15

Round bracket apparent syntactic error in C++ but compiler not complaining

Why the compiler (g++-4.9) is not complaining on this notation?
double d=(4,5,6);
and if I debug it, the value of d is 6?
What do the round brackets mean in this expression?
P.S.
I've enabled C++11
This uses the comma operator, which (without overloading) just evaluates the left-hand expression, throws away the result, and returns the result of the right-hand expression.
Since the expressions 4 and 5 have no side-effects, your code is equivalent to:
double d = 6;
This is comma operator.
In a comma expression E1, E2, the expression E1 is evaluated, its
result is discarded, and its side effects are completed before
evaluation of the expression E2 begins (note that a user-defined
operator, cannot guarantee sequencing).
For (4,5,6), first evaluate expression 4, disregard its return value and complete any side-effects (nothing here indeed), then do the same thing with 5, then evaluate the last expression 6, returning the type and the result of this evaluation.

Why does C++ accept multiple prefixes but not postfixes for a variable

While looking into Can you have a incrementor and a decrementor on the same variable in the same statement in c
I discovered that you can have several prefix increment/decrement operators on a single variable, but only one postfix
ex:
++--++foo; // valid
foo++--++; // invalid
--foo++; // invalid
Why is this?
This is due to the fact that in C++ (but not C), the result of ++x is a lValue, meaning it is assignable, and thus chain-able.
However, the result of x++ is NOT an lValue, instead it is a prValue, meaning it cannot be assigned to, and thus cannot be chained.
In C++ language prefix increment/decrement operators return lvalues, while postfix ones return rvalues. Meanwhile, all modifying operators require lvalue arguments. This means that the result of prefix increment/decrement can be passed on to any other additional operator that requires an lvalue argument (including additional increments/decrements).
For the very same reason in C++ you can write code like this
int i = 0;
int *p = &++i;
which will increment i and make p point to i. Unary & requires lvalue operand, which is why it will work with the result of prefix ++ (but not with postfix one).
Expressions with multiple built-in prefix increments/decrements applied to the same object produce undefined behavior, but they are nevertheless well-formed (i.e. "compilable").
Expressions like ++foo-- are invalid because in C++ postfix operators have higher precedence than prefix ones. Braces can change that. For example, (++foo)-- is a well-formed expression, albeit leading to undefined behavior again.