TL; DR
Parênteses extras mudam o significado de um programa C ++ nos seguintes contextos:
- evitando a procura de nome dependente de argumento
- habilitando o operador vírgula em contextos de lista
- resolução de ambigüidade de análises complicadas
- deduzindo referencia em
decltype
expressões
- evitando erros de macro do pré-processador
Evitando a procura de nome dependente de argumento
Conforme detalhado no Anexo A da Norma, a post-fix expression
do formulário (expression)
é um primary expression
, mas não um id-expression
e, portanto, não é um unqualified-id
. Isso significa que a pesquisa de nome dependente de argumento é evitada em chamadas de função do formulário em (fun)(arg)
comparação com o formato convencional fun(arg)
.
3.4.2 Pesquisa de nome dependente de argumento [basic.lookup.argdep]
1 Quando a expressão pós-fixada em uma chamada de função (5.2.2) é um id não qualificado , outros namespaces não considerados durante a pesquisa não qualificada usual (3.4.1) podem ser pesquisados, e nesses namespaces, função amiga do escopo de namespace ou declarações de template de função (11.3) não visíveis de outra forma podem ser encontradas. Essas modificações na pesquisa dependem dos tipos de argumentos (e para argumentos de template de template, o namespace do argumento do template). [Exemplo:
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 exemplo]
Ativando o operador vírgula em contextos de lista
O operador vírgula tem um significado especial na maioria dos contextos do tipo lista (argumentos de função e modelo, listas de inicializadores, etc.). Parênteses do formulário a, (b, c), d
em tais contextos podem habilitar o operador vírgula em comparação com o formulário regular a, b, c, d
onde o operador vírgula não se aplica.
5.18 Operador vírgula [expr.comma]
2 Em contextos onde a vírgula recebe um significado especial, [Exemplo: em listas de argumentos para funções (5.2.2) e listas de inicializadores (8.5) - finalizar exemplo], o operador vírgula, conforme descrito na Cláusula 5, pode aparecer apenas entre parênteses. [Exemplo:
f(a, (t=3, t+2), c);
tem três argumentos, o segundo dos quais tem o valor 5. —enviar exemplo]
Resolução de ambiguidade de análises incômodas
A compatibilidade com versões anteriores com C e sua sintaxe de declaração de função misteriosa pode levar a surpreendentes ambigüidades de análise, conhecidas como análises complicadas. Essencialmente, qualquer coisa que possa ser analisada como uma declaração será analisada como uma , mesmo que uma análise concorrente também se aplique.
6.8 Resolução de ambigüidade [stmt.ambig]
1 Há uma ambigüidade na gramática envolvendo declarações de expressão e declarações : Uma declaração de expressão com uma conversão de tipo explícita de estilo de função (5.2.3) como sua subexpressão mais à esquerda pode ser indistinguível de uma declaração onde o primeiro declarador começa com um ( . nesses casos, a declaração é uma declaração .
8.2 Resolução de ambigüidade [dcl.ambig.res]
1 A ambigüidade que surge da semelhança entre um elenco de estilo de função e uma declaração mencionada em 6.8 também pode ocorrer no contexto de uma declaração . Nesse contexto, a escolha é entre uma declaração de função com um conjunto redundante de parênteses em torno de um nome de parâmetro e uma declaração de objeto com uma conversão de estilo de função como inicializador. Assim como para as ambigüidades mencionadas em 6.8, a resolução é considerar qualquer constructo que possa ser uma declaração uma declaração . [Nota: Uma declaração pode ser explicitamente eliminada da ambigüidade por uma conversão de estilo não funcional, por um = para indicar a inicialização ou removendo os parênteses redundantes em torno do nome do parâmetro. —Enviar nota] [Exemplo:
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 exemplo]
Um exemplo famoso disso é o mais irritante Parse , um nome popularizado por Scott Meyers no item 6 de seu livro Effective STL :
ifstream dataFile("ints.dat");
list<int> data(istream_iterator<int>(dataFile), // warning! this doesn't do
istream_iterator<int>()); // what you think it does
Isso declara uma função,, data
cujo tipo de retorno é list<int>
. Os dados da função têm dois parâmetros:
- O primeiro parâmetro é nomeado
dataFile
. Seu tipo é istream_iterator<int>
. Os parênteses ao redor dataFile
são supérfluos e são ignorados.
- O segundo parâmetro não tem nome. Seu tipo é um ponteiro para a função não pegando nada e retornando um
istream_iterator<int>
.
Colocar parênteses extras ao redor do primeiro argumento da função (parênteses ao redor do segundo argumento são ilegais) irá resolver a ambigüidade
list<int> data((istream_iterator<int>(dataFile)), // note new parens
istream_iterator<int>()); // around first argument
// to list's constructor
C ++ 11 tem sintaxe de inicializador de chave que permite contornar tais problemas de análise em muitos contextos.
Deduzindo referencia em decltype
expressões
Em contraste com a auto
dedução de tipo, decltype
permite que a referência (referências lvalue e rvalue) seja deduzida. As regras distinguem entre expressões decltype(e)
e decltype((e))
:
7.1.6.2 Especificadores de tipo simples [dcl.type.simple]
4 Para uma expressão e
, o tipo denotado pordecltype(e)
é definido como segue:
- se e
for uma expressão de id sem parênteses ou um acesso de membro de classe sem parênteses (5.2.5), decltype(e)
é o tipo da entidade nomeada por e
. Se não houver tal entidade, ou se e
nomear um conjunto de funções sobrecarregadas, o programa está malformado;
- caso contrário, se e
é um valor x, decltype(e)
é T&&
, onde T
é o tipo de e
;
- caso contrário, se e
é um lvalue, decltype(e)
é T&
, onde T
é o tipo de e
;
- caso contrário, decltype(e)
é o tipo de e
.
O operando do especificador decltype é um operando não avaliado (Cláusula 5). [Exemplo:
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&
—Enviar exemplo] [Nota: As regras para determinar os tipos de envolvimento
decltype(auto)
são especificadas em 7.1.6.4. —Enviar nota]
As regras para decltype(auto)
têm um significado semelhante para parênteses extras no RHS da expressão de inicialização. Aqui está um exemplo do FAQ de C ++ e este Q&A relacionado
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
O primeiro retorna string
, o segundo retorna string &
, que é uma referência à variável local str
.
Prevenção de erros relacionados à macro do pré-processador
Há uma série de sutilezas com macros de pré-processador em sua interação com a linguagem C ++ adequada, as mais comuns das quais estão listadas abaixo
- usando parênteses em torno dos parâmetros da macro dentro da definição da macro
#define TIMES(A, B) (A) * (B);
para evitar a precedência indesejada do operador (por exemplo, em TIMES(1 + 2, 2 + 1)
que resulta em 9, mas resultaria em 6 sem os parênteses em volta (A)
e(B)
- usando parênteses em torno de argumentos de macro contendo vírgulas:
assert((std::is_same<int, int>::value));
que de outra forma não seriam compilados
- usar parênteses em torno de uma função para proteger contra expansão de macro nos cabeçalhos incluídos:
(min)(a, b)
(com o efeito colateral indesejado de também desativar o ADL)
&(C::f)
, o operando de&
permaneceC::f
, não é?