TL; DR
Los paréntesis adicionales cambian el significado de un programa C ++ en los siguientes contextos:
- evitar la búsqueda de nombres dependientes de argumentos
- habilitar el operador de coma en contextos de lista
- resolución de ambigüedad de análisis molestos
- deducir referencias en
decltype
expresiones
- prevenir errores de macro del preprocesador
Evitar la búsqueda de nombres dependiente de argumentos
Como se detalla en el Anexo A de la Norma, un post-fix expression
del formulario (expression)
es un primary expression
, pero no un id-expression
, y por lo tanto no un unqualified-id
. Esto significa que la búsqueda de nombres dependientes de argumentos se evita en las llamadas a funciones de la forma en (fun)(arg)
comparación con la forma convencional fun(arg)
.
3.4.2 Búsqueda de nombre dependiente del argumento [basic.lookup.argdep]
1 Cuando la expresión-postfijo en una llamada de función (5.2.2) es un id-no calificado , se pueden buscar otros espacios de nombres no considerados durante la búsqueda no calificada habitual (3.4.1), y en esos espacios de nombres, la función amiga del ámbito de nombres o Se pueden encontrar declaraciones de plantilla de función (11.3) que de otro modo no serían visibles. Estas modificaciones a la búsqueda dependen de los tipos de argumentos (y para los argumentos de plantilla de plantilla, el espacio de nombres del argumento de plantilla). [Ejemplo:
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
}
—Ejemplo final]
Habilitar el operador de coma en contextos de lista
El operador de coma tiene un significado especial en la mayoría de los contextos similares a listas (argumentos de función y plantilla, listas de inicializadores, etc.). Los paréntesis del formulario a, (b, c), d
en tales contextos pueden habilitar el operador de coma en comparación con el formulario regular a, b, c, d
donde no se aplica el operador de coma.
5.18 Operador de coma [expr.comma]
2 En contextos donde la coma tiene un significado especial, [Ejemplo: en listas de argumentos para funciones (5.2.2) y listas de inicializadores (8.5) —ejemplo final], el operador de coma como se describe en la Cláusula 5 puede aparecer sólo entre paréntesis. [Ejemplo:
f(a, (t=3, t+2), c);
tiene tres argumentos, el segundo de los cuales tiene el valor 5. —ejemplo final]
Resolución de ambigüedad de análisis molestos
La compatibilidad con versiones anteriores de C y su sintaxis de declaración de función arcana puede conducir a sorprendentes ambigüedades de análisis, conocidas como análisis molestos. Básicamente, todo lo que se pueda analizar como una declaración se analizará como uno , aunque también se aplicaría un análisis de la competencia.
6.8 Resolución de ambigüedad [stmt.ambig]
1 Existe una ambigüedad en la gramática que involucra declaraciones-expresión y declaraciones : Una declaración-expresión con una conversión de tipo explícita de estilo de función (5.2.3) como su subexpresión más a la izquierda puede ser indistinguible de una declaración donde el primer declarador comienza con un ( . En aquellos casos en que la declaración es una declaración .
8.2 Resolución de ambigüedad [dcl.ambig.res]
1 La ambigüedad que surge de la similitud entre un reparto de estilo de función y una declaración mencionada en 6.8 también puede ocurrir en el contexto de una declaración . En ese contexto, la elección es entre una declaración de función con un conjunto redundante de paréntesis alrededor de un nombre de parámetro y una declaración de objeto con una conversión de estilo de función como inicializador. Al igual que para las ambigüedades mencionadas en 6.8, la resolución es considerar cualquier construcción que posiblemente podría ser una declaración como una declaración . [Nota: Una declaración se puede eliminar explícitamente de la ambigüedad mediante una conversión de estilo sin función, con un = para indicar la inicialización o eliminando los paréntesis redundantes alrededor del nombre del parámetro. —End note] [Ejemplo:
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
}
—Ejemplo final]
Un ejemplo famoso de esto es Most Vexing Parse , un nombre popularizado por Scott Meyers en el artículo 6 de su libro 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
Esto declara una función data
, cuyo tipo de retorno es list<int>
. Los datos de la función toman dos parámetros:
- El primer parámetro se nombra
dataFile
. Su tipo es istream_iterator<int>
. Los paréntesis alrededor dataFile
son superfluos y se ignoran.
- El segundo parámetro no tiene nombre. Su tipo es puntero para funcionar sin tomar nada y devolver un
istream_iterator<int>
.
Colocar paréntesis adicionales alrededor del primer argumento de la función (los paréntesis alrededor del segundo argumento son ilegales) resolverá la ambigüedad
list<int> data((istream_iterator<int>(dataFile)), // note new parens
istream_iterator<int>()); // around first argument
// to list's constructor
C ++ 11 tiene una sintaxis de inicializador de llaves que permite evitar estos problemas de análisis en muchos contextos.
Deducir referencias en decltype
expresiones
A diferencia de la auto
deducción de tipo, decltype
permite deducir la referencia (referencias lvalue y rvalue). Las reglas distinguen entre expresiones decltype(e)
y decltype((e))
:
7.1.6.2 Especificadores de tipo simple [dcl.type.simple]
4 Para una expresión e
, el tipo denotado pordecltype(e)
se define como sigue:
- si e
es una expresión-id sin paréntesis o un acceso de miembro de clase sin paréntesis (5.2.5), decltype(e)
es el tipo de entidad nombrada por e
. Si no existe tal entidad, o si e
nombra un conjunto de funciones sobrecargadas, el programa está mal formado;
- en caso contrario, si e
es un valor x, decltype(e)
es T&&
, donde T
es el tipo de e
;
- de lo contrario, si e
es un lvalue, decltype(e)
es T&
, donde T
es el tipo de e
;
- de lo contrario, decltype(e)
es el tipo de e
.
El operando del especificador decltype es un operando no evaluado (cláusula 5). [Ejemplo:
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&
decltype(auto)
—Ejemplo final ] [Nota: Las reglas para determinar los tipos de participación
se especifican en 7.1.6.4. —Nota final]
Las reglas para decltype(auto)
tienen un significado similar para paréntesis adicionales en el RHS de la expresión de inicialización. Aquí hay un ejemplo de las preguntas frecuentes de C ++ y estas preguntas y respuestas relacionadas
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
El primero devuelve string
, el segundo devuelve string &
, que es una referencia a la variable local str
.
Prevención de errores relacionados con la macro del preprocesador
Existe una gran cantidad de sutilezas con las macros de preprocesador en su interacción con el lenguaje C ++ propiamente dicho, las más comunes de las cuales se enumeran a continuación.
- utilizando paréntesis alrededor de los parámetros de macro dentro de la definición de macro
#define TIMES(A, B) (A) * (B);
para evitar la precedencia de operadores no deseados (por ejemplo, en el TIMES(1 + 2, 2 + 1)
que produce 9 pero produciría 6 sin los paréntesis alrededor (A)
y(B)
- usando paréntesis alrededor de los argumentos macro que tienen comas dentro:
assert((std::is_same<int, int>::value));
que de otra manera no se compilarían
- usar paréntesis alrededor de una función para proteger contra la expansión de macros en los encabezados incluidos:
(min)(a, b)
(con el efecto secundario no deseado de deshabilitar también ADL)
&(C::f)
, el operando de&
sigue siendoC::f
, ¿no es así?