When Do Extra Parentheses Have an Effect, Other Than on Operator Precedence

When do extra parentheses have an effect, other than on operator precedence?


TL;DR

Extra parentheses change the meaning of a C++ program in the following contexts:

  • preventing argument-dependent name lookup
  • enabling the comma operator in list contexts
  • ambiguity resolution of vexing parses
  • deducing referenceness in decltype expressions
  • preventing preprocessor macro errors

Preventing argument-dependent name lookup

As is detailed in Annex A of the Standard, a post-fix expression of the form (expression) is a primary expression, but not an id-expression, and therefore not an unqualified-id. This means that argument-dependent name lookup is prevented in function calls of the form (fun)(arg) compared to the conventional form fun(arg).

3.4.2 Argument-dependent name lookup [basic.lookup.argdep]

1 When the postfix-expression in a function call (5.2.2) is an
unqualified-id
, other namespaces not considered during the usual
unqualified lookup (3.4.1) may be searched, and in those namespaces,
namespace-scope friend function or function template declarations
(11.3) not otherwise visible may be found. These modifications to the
search depend on the types of the arguments (and for template template
arguments, the namespace of the template argument). [ Example:

namespace N {
struct S { };
void f(S);
}

void g() {
N::S s;
f(s); // OK: calls N::f
(f)(s); // error: N::f not considered; parentheses
// prevent argument-dependent lookup
}

—end example ]

Enabling the comma operator in list contexts

The comma operator has a special meaning in most list-like contexts (function and template arguments, initializer lists etc.). Parentheses of the form a, (b, c), d in such contexts can enable the comma operator compared to the regular form a, b, c, d where the comma operator does not apply.

5.18 Comma operator [expr.comma]

2 In contexts where comma is given a special meaning, [ Example: in
lists of arguments to functions (5.2.2) and lists of initializers
(8.5)
—end example ] the comma operator as described in Clause 5 can
appear only in parentheses. [ Example:

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

has three arguments, the second of which has the value 5. —end example
]

Ambiguity resolution of vexing parses

Backward compatibility with C and its arcane function declaration syntax can lead to surprising parsing ambiguities, known as vexing parses. Essentially, anything that can be parsed as a declaration will be parsed as one, even though a competing parse would also apply.

6.8 Ambiguity resolution [stmt.ambig]

1 There is an ambiguity in the grammar involving expression-statements
and declarations
: An expression-statement with a function-style
explicit type conversion (5.2.3) as its leftmost subexpression can be
indistinguishable from a declaration where the first declarator starts
with a (. In those cases the statement is a declaration.

8.2 Ambiguity resolution [dcl.ambig.res]

1 The ambiguity arising from the similarity between a function-style
cast and a declaration mentioned in 6.8 can also occur in the context
of a declaration
. In that context, the choice is between a function
declaration with a redundant set of parentheses around a parameter
name and an object declaration with a function-style cast as the
initializer. Just as for the ambiguities mentioned in 6.8, the
resolution is to consider any construct that could possibly be a
declaration a declaration
. [ Note: A declaration can be explicitly
disambiguated by a nonfunction-style cast, by an = to indicate
initialization or by removing the redundant parentheses around the
parameter name. —end note ] [ Example:

struct S {
S(int);
};

void foo(double a) {
S w(int(a)); // function declaration
S x(int()); // function declaration
S y((int)a); // object declaration
S z = int(a); // object declaration
}

—end example ]

A famous example of this is the Most Vexing Parse, a name popularized by Scott Meyers in Item 6 of his Effective STL book:

ifstream dataFile("ints.dat");
list<int> data(istream_iterator<int>(dataFile), // warning! this doesn't do
istream_iterator<int>()); // what you think it does

This declares a function, data, whose return type is list<int>. The
function data takes two parameters:

  • The first parameter is named dataFile. It's type is istream_iterator<int>. The
    parentheses around dataFile are superfluous and are ignored.
  • The second parameter has no name. Its type is pointer to function taking
    nothing and returning an istream_iterator<int>.

Placing extra parentheses around the first function argument (parentheses around the second argument are illegal) will resolve the ambiguity

list<int> data((istream_iterator<int>(dataFile)), // note new parens
istream_iterator<int>()); // around first argument
// to list's constructor

C++11 has brace-initializer syntax that allows to side-step such parsing problems in many contexts.

Deducing referenceness in decltype expressions

In contrast to auto type deduction, decltype allows referenceness (lvalue and rvalue references) to be deduced. The rules distinguish between decltype(e) and decltype((e)) expressions:

7.1.6.2 Simple type specifiers [dcl.type.simple]

4 For an expression e, the type denoted by decltype(e) is defined as
follows:

— if e is an unparenthesized id-expression or an
unparenthesized class member access (5.2.5), decltype(e) is the type
of the entity named by e. If there is no such entity, or if e names a
set of overloaded functions, the program is ill-formed;

— otherwise,
if e is an xvalue, decltype(e) is T&&, where T is the type of e;


otherwise, if e is an lvalue, decltype(e) is T&, where T is the type
of e;

— otherwise, decltype(e) is the type of e.

The operand of the
decltype specifier is an unevaluated operand (Clause 5). [ Example:

const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1 = 0; // type is const int&&
decltype(i) x2; // type is int
decltype(a->x) x3; // type is double
decltype((a->x)) x4 = x3; // type is const double&

—end example ] [ Note: The rules for determining types involving
decltype(auto) are specified in 7.1.6.4. —end note ]

The rules for decltype(auto) have a similar meaning for extra parentheses in the RHS of the initializing expression. Here's an example from the C++FAQ and this related Q&A

decltype(auto) look_up_a_string_1() { auto str = lookup1(); return str; }  //A
decltype(auto) look_up_a_string_2() { auto str = lookup1(); return(str); } //B

The first returns string, the second returns string &, which is a reference to the local variable str.

Preventing preprocessor macro related errors

There is a host of subtleties with preprocessor macros in their interaction with the C++ language proper, the most common of which are listed below

  • using parentheses around macro parameters inside the macro definition #define TIMES(A, B) (A) * (B); in order to avoid unwanted operator precedence (e.g. in TIMES(1 + 2, 2 + 1) which yields 9 but would yield 6 without the parentheses around (A) and (B)
  • using parentheses around macro arguments having commas inside: assert((std::is_same<int, int>::value)); which would otherwise not compile
  • using parentheses around a function to protect against macro expansion in included headers: (min)(a, b) (with the unwanted side effect of also disabling ADL)

If parenthesis has a higher precedence then why is increment operator solved first?

Expressions are evaluated left to right. Parentheses (and precedence) just express grouping, they don't express ordering of evaluation.

So

 11 * (12 + 5)
++a ++a

equals

187

Operator Precedence in declaring more pointers in one instruction

There is no any precedence rule. The gramma of a simple declaration looks like

decl-specifier-seq init-declarator-listopt ; 

The sign * belongs to declarators not to decl-specidier-seq as for example type specifier int.

So you may for example rewrite the declaration

int * p1, * p2;

like

int ( * p1 ), ( * p2 );

where ( *p1 ) and ( *p2 ) are decalartors (thouhg in this case the parentheses are redundant)

You may not write for example

( int * ) p1, p2;

The compiler will issue an error.

Parentheses are required when a more complicated type is declared. For example let's declare a pointer to an array

int ( *p )[N];

where N is some constant.

So you may enclose declarators in parentheses.

Let's consider a more complicated declaration: of a function that returns a pointer to function and has as a parameter another function

void ( *f( int cmp( const void *, const void * ) )( int *a );

As for the precedence then rules of building declarators describe them in the gramma

For example

if you will write

int * a[10];

then it is an array of 10 elements of the type int *.

However if you will write

int ( *a[10] ); 

then its an array of 10 pointers to objects of the type int.

If you will write

int ( *a )[10];

then it is a pointer to an array of 10 integers.

Take into account that typedef is also a decl-specifier.

So for example this typedef

typedef int *intPtr;

you may rewrite like

int typedef *intPtr;

or even like

int typedef ( *intPtr );

One more example of declaration. Let's consider a multidimensional array. In can be declared like

int ( ( ( a )[N1] )[N2] );

though again the parentheses are redundant. However they can help to understand how arrays are implicitly converted to pointers to their first elements in expressions.

For example if you have an array

int a[N1][N2];

then to get a pointer declaration to its first element you can rewrite the declaration ,like

int ( a[N1] )[N2];

and now substitute a[N1] for *a (or for example *p).

int ( *p )[N2] = a;

why parenthesis is unable to change c++ operator precedence in this case?


i definitely understand ++x guys, my question is about change operator precedence using ()

Operator precedence has nothing to do with this.

The misunderstanding probably isn't your fault: you've likely been mistaught. Your teacher(s) told you that an operand with a higher precedence, than some other operand, will be "executed first".

While this is a common explanation in schools, it is not true.

There are three things that can change the meaning of an expression in this sense:

 

  1. Operator precedence

    This is merely a set of rules that tell us, and tell the compiler, which operands go to which operator. Like, in 3 + 5 * 7, do we pass 3+5 to the multiplication operator, or do we pass 5*7 to the addition operator? It's about parsing.

     

  2. Evaluation order

    Each operand then needs to be evaluated to produce a value (e.g. 3+5 becomes 8, or 5*7 becomes 35). The rules on the order in which these evaluations happen are quite complicated in C++, more so than you might expect, but you usually don't have to worry about them unless you're doing crazy things in between sequence points (to borrow pre-C++11 parlance).

    (This is the closest you'll get to a notion of "will be executed first".)

     

  3. The meaning of the operator

    This is where you're coming unstuck here. The meaning of the postfix increment operator x++ is "increment x, and evaluate to the old value". Period. Full stop.

    It doesn't matter which operator precedence rules led to the expression x++ being evaluated (as opposed to some other interpretation of the symbols in your code): when it's evaluated, whenever it's evaluated, you get the old value for x.

    The meaning of the prefix increment operator ++x, however, is "increment x, and evaluate to the new value", and that's the behaviour you want, so that's the code you should write.

     

Ultimately, what sequence of computer instructions actually produces this behaviour is completely up to the compiler, and can be surprising. You shouldn't worry about it, as long as the program's result is as specified in the standard.

So just forget about this "will be executed first" stuff; it's rubbish.

When does C++11 do arithmetic type conversions relative to operator precedence?

The expression

val1 + val2 * val3 * val4

has the type of double, but the multiplication part is of type int32_t. If we apply the rules on how it is evaluated we have

val1 + (val2 * val3 * val4)

because multiplication has a higher precedence, it will be evaluated without considering the type of val1 and since all of the operands have the same type the result type will be the same as the operands. That result is then converted to a double and added to val1. This behavior has not changed in any of the versions of C++.

To get the multiplication to happen as double you need to cast either val2 or val3 to a double, which will make the entire multiplication part evaluated as a double. That would look like

val1 + (static_cast<double>(val2) * val3 * val4)

the parentheses are not needed but I like using them to show the grouping

Why operator cannot be in parentheses?

§ 7.6.6 (expr.add) defines "additive expressions" as:

additive-expression:
multiplicative-expression
additive-expression + multiplicative-expression
additive-expression - multiplicative-expression

No parens around the operator allowed.

Operator Precedence.. () and ++

As usual, this is undefined behaviour. There is no sequence point at the +, so it is not defined at what point the ++ updates a. This is not a precedence issue.

How does operator precedence actually work in this program?

Operator precedence (and associativity) only determines how the expression should be parsed. It is a common mistake to confuse it with order of evaluation of the operands, which is different thing. Operator precedence is rather irrelevant in this example.

For most operators in C, the order of evaluation of the operands is not specified. Had you written true | l++ then l++ would have been executed. The "short-circuit evaluation" is the reason why this doesn't happen in your code. The && || operators is a special case, since they explicitly define the order of evaluation. The right operand of || is guaranteed not to be evaluated in case the left operand evaluates to non-zero.



Related Topics



Leave a reply



Submit