14

I am a bit confused withnthe interpretation of parentheses by the compiler. Can some one please explain what actually happens in such contexts?

Casting: (int)a or int(a)

Parameter passing:

template <typename t>
int size(t (&)[n]){return n;}

Obviously there could be many different contexts where parentheses change the meaning or interpretation. Can some one please explain what exaactly is happening behind the curtain? How does the compiler know how to interpret in each context? Is there a general guideline or is it a specific rule for each case?

Thanks

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
Kiran
  • 5,478
  • 13
  • 56
  • 84

3 Answers3

27

Captain Pedantic to the Rescue!

If you write

int(value)

This is what's known as an explicit type conversion and is governed by §5.2.3. The exact wording says that

A simple-type-specifier (7.1.5) followed by a parenthesized expression-list constructs a value of the specified type given the expression list. If the expression list is a single expression, the type conversion expression is equivalent (in definedness, and if defined in meaning) to the corresponding cast expression (5.4)

(My emphasis). So this means that

int(value)

and

(int)value

are completely identical to one another. It's up to you to pick whichever of these you find easier to write.

As for your second question, in the example you gave with the templates and array, I believe that what you meant to write was something like this.

template <typename T, size_t N>
size_t (T (&)[N]) {
    return N;
}

Here, N as well as T is a template parameter, which allows you to pass in any array that you'd like while having the compiler fill in N with the number of elements in the array. In case this looks confusing (what on earth is T (&)[N]?), it's because this function is taking in a parameter of type T (&)[N]. To make this a bit easier to read, let's give this parameter a name, as shown here:

template <typename T, size_t N>
size_t (T (&array)[N]) {
    return N;
}

I think this makes this a bit easier to read. But what does this declaration mean?

T (&array)[N]

This declares a variable called array that is a reference to an array of Ts of exactly N elements. You can indeed declare references to arrays, just as you can declare pointers to arrays. This is not very common in practice, but in this particular template idiom is a great way of having the compiler infer the size of the array for you as it tries to match the array to the template argument.

The reason for the parentheses in this case is that if you write

T& array[N]

The compiler would parse this as "a variable called array that's an array of N objects, each of which is a T&. However, the C++ spec specifically disallows arrays of references, and this would be illegal. The parentheses explicitly disambiguate this. This is similar to function pointers - you write

void (*functionPointer)()

instead of

void *functionPointer()

To make the compiler realize that the * means that functionPointer is a pointer, rather than a function that returns a void *.

As for how the compiler determines when to treat parentheses in each way, the rules are fairly complex and there are actually a few circumstances in which the compiler will not parse your expression in the intended way. One of these cases is something colloquially referred to as "the most vexing parse" in which the compiler treats what looks like object construction as a function prototype. As an example, this code:

vector<int> v();

Does not create a vector<int> called v initialized using the default constructor. Instead, it treats this as a function prototype for a function called v that takes no arguments and produces a vector<int>! However, if you were to write

vector<int> v(10);

Then the compiler can unambiguously infer that this is a declaration of a vector<int> passing 10 as a constructor argument, because there's no way that it could be treated as a function prototype. §6.8 and §8.2 of the spec handles these cases by saying that anything that can be treated as a declaration will be, and anything that can be treated as a function prototype will be as well.

The case of parentheses in the context of the array (that is, T (&array)[N]) is handled by a different piece of logic because in the context in which you're declaring a variable or defining a parameter whose type requires explicit parenthesis, there can be no ambiguity about your intention because it's clear from context that you're naming a type in order to declare a variable.

To summarize -

  1. Casts of the form T(value) and (T)value are identical.
  2. The parentheses in T (&array)[N] are to prevent the compiler from binding the & to T instead of to array as intended.
  3. The particular use of parenthesis is usually inferred from context, though some issues can come up between variable declarations and function prototypes.

Hope this helps!

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
  • Awesome. Thanks for the detailed explanation. I am goingnto read it again and again multiple times until it syncs into my thoughtnprocess. – Kiran Feb 19 '11 at 15:11
  • "Casts of the form `T(value)` and `(T)value` are identical.". That's simply not true. The latter can perform a reinterpret_cast while the former can't. For example, converting a void pointer to a specific pointer doesn't work with the former and will result in a compiler error. – Jimmy T. Jun 09 '22 at 20:17
  • @JimmyT. I wrote this answer back in 2011 when I was using the C++03 spec as a reference. I'm wondering whether (a) my answer was always incorrect or (b) this is correct in C++03 but false in later versions. Any thoughts on that? – templatetypedef Jun 09 '22 at 21:40
  • @templatetypedef Looking at the spec again it seems like you were right after all and as far as I understand it the newer specs didn't change this – Jimmy T. Jun 10 '22 at 10:36
5

casting (int)a or int(a)

(int)a is a cast

int(a) is the construction of an int, passing in a to the int ctor

Expressions are evaluated according to operators' precedence, arity, and whether the operator is right or left associative. Read the operator precedence chart in your C++ text.

Get a copy of the program c++decl; it reads C++ expressions and outputs an English langauge explanation of the expression. Or read this explanation.

tpdi
  • 34,554
  • 11
  • 80
  • 120
  • Thats true. But this doesnt apply to the second case. How does the compiler interpret in that case? Thx – Kiran Feb 19 '11 at 05:16
  • Also http://cdecl.org is useful but doesn't fully support C++. (As a test, it described a reference correctly, but said it's unsupported in C). – RastaJedi Aug 10 '16 at 22:29
0

From C++14 Appendix A, the complete list of cases where parentheses may appear in the grammar is:

§A.14 Preprocessing directives
control-line: # define identifier lparen identifier-list_opt ) replacement-list new-line
control-line: # define identifier lparen ... ) replacement-list new-line
control-line: # define identifier lparen identifier-list , ... ) replacement-list new-line

§A.2 Lexical conventions
raw-string: " d-char-sequence_opt ( r-char-sequence_opt ) d-char-sequence_opt "

§A.4 Expressions
primary-expression: ( expression )
lambda-declarator: ( parameter-declaration-clause ) mutable_opt exception-specification_opt attribute-specifier-seq_opt trailing-return-type_opt
postfix-expression: const_cast < type-id > ( expression )
postfix-expression: dynamic_cast < type-id > ( expression )
postfix-expression: postfix-expression ( expression-list_opt )
postfix-expression: reinterpret_cast < type-id > ( expression )
postfix-expression: simple-type-specifier ( expression-list_opt )
postfix-expression: static_cast < type-id > ( expression )
postfix-expression: typeid ( expression )
postfix-expression: typeid ( type-id )
postfix-expression: typename-specifier ( expression-list_opt )
unary-expression: alignof ( type-id )
unary-expression: sizeof ( type-id )
unary-expression: sizeof ... ( identifier )
new-expression: ::_opt new new-placement_opt ( type-id ) new-initializer_opt
new-placement: ( expression-list )
new-initializer: ( expression-list_opt )
noexcept-expression: noexcept ( expression )
cast-expression: ( type-id ) cast-expression

§A.5 Statements
selection-statement: if ( condition ) statement
selection-statement: if ( condition ) statement else statement
selection-statement: switch ( condition ) statement
iteration-statement: do statement while ( expression ) ;
iteration-statement: for ( for-init-statement condition_opt ; expression_opt ) statement
iteration-statement: for ( for-range-declaration : for-range-initializer ) statement
iteration-statement: while ( condition ) statement

§A.6 Declarations
static_assert-declaration: static_assert ( constant-expression , string-literal ) ;
decltype-specifier: decltype ( auto )
decltype-specifier: decltype ( expression )
asm-definition: asm ( string-literal ) ;
alignment-specifier: alignas ( assignment-expression ..._opt )
alignment-specifier: alignas ( type-id ..._opt )
attribute-argument-clause: ( balanced-token-seq )
balanced-token: ( balanced-token-seq )

§A.7 Declarators
noptr-declarator: ( ptr-declarator )
parameters-and-qualifiers: ( parameter-declaration-clause ) attribute-specifier-seq_opt cv-qualifier-seq_opt ref-qualifier_opt exception-specification_opt
noptr-abstract-declarator: ( ptr-abstract-declarator )
initializer: ( expression-list )

§A.10 Special member functions
mem-initializer: mem-initializer-id ( expression-list_opt )

§A.11 Overloading
operator-function-id: operator ( )

§A.13 Exception handling
handler: catch ( exception-declaration ) compound-statement
dynamic-exception-specification: throw ( type-id-list_opt )
noexcept-specification: noexcept ( constant-expression )

Note that:

  • The preprocessor rules for if-group and elif-group do refer to constant-expression.
  • lparen means a ( with no preceding whitespace
  • The rule for raw-string is during lexing, so the ( and ) do not become tokens.
  • Any sequence of valid tokens can appear in a preprocessor group whose condition evaluates to false.

In your question, you use the following:

  • cast-expression: ( type-id ) cast-expression
  • postfix-expression: simple-type-specifier ( expression-list_opt )
  • parameters-and-qualifiers: ( parameter-declaration-clause ) attribute-specifier-seq_opt cv-qualifier-seq_opt ref-qualifier_opt exception-specification_opt
  • noptr-abstract-declarator: ( ptr-abstract-declarator )
o11c
  • 15,265
  • 4
  • 50
  • 75