c++ - programacion - tabla de precedencia de operadores




¿Cuándo los paréntesis adicionales tienen un efecto, que no sea la precedencia del operador? (2)

Los paréntesis en C ++ se usan en muchos lugares: por ejemplo, en llamadas a funciones y expresiones de agrupación para anular la precedencia del operador. Además de los paréntesis extra ilegales (como las listas de argumentos de llamadas a funciones), una regla general -pero no absoluta- de C ++ es que los paréntesis adicionales nunca duelen :

5.1 Expresiones primarias [expr.prim]

5.1.1 General [expr.prim.general]

6 Una expresión entre paréntesis es una expresión primaria cuyo tipo y valor son idénticos a los de la expresión adjunta. La presencia de paréntesis no afecta si la expresión es un valor l. La expresión entre paréntesis se puede usar exactamente en los mismos contextos donde se puede usar la expresión adjunta, y con el mismo significado, excepto que se indique lo contrario .

Pregunta : ¿en qué contextos los paréntesis adicionales cambian el significado de un programa C ++, aparte de anular la precedencia básica del operador?

NOTA : Considero que la restricción de la sintaxis del puntero al miembro con el &qualified-id sin paréntesis está fuera del alcance porque restringe la sintaxis en lugar de permitir dos sintaxis con diferentes significados. Del mismo modo, el uso de paréntesis dentro de las definiciones de macro del preprocesador también protege contra la precedencia del operador no deseado.


TL; DR

Los paréntesis adicionales cambian el significado de un programa C ++ en los siguientes contextos:

  • evitando la búsqueda de nombres dependiente del argumento
  • habilitar el operador de coma en contextos de lista
  • resolución de ambigüedad de análisis molestos
  • deducir referencia en expresiones decltype
  • prevenir errores de macro del preprocesador

Evitar la búsqueda de nombres dependiente del argumento

Como se detalla en el Anexo A de la Norma, una post-fix expression de la forma (expression) es una primary expression , pero no una id-expression , y por lo tanto no una unqualified-id no unqualified-id . Esto significa que la búsqueda de nombres dependiente del argumento se previene en las llamadas a función de la forma (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 postfix en una llamada a función (5.2.2) es una identificación no calificada, 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 espacio de nombres o las declaraciones de la plantilla de función (11.3) no visibles de otra manera se pueden encontrar. Estas modificaciones en la búsqueda dependen de los tipos de argumentos (y para los argumentos de la plantilla de plantilla, el espacio de nombres del argumento de la 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
}

-Final ejemplo]

Habilitación del operador de coma en contextos de lista

El operador de coma tiene un significado especial en la mayoría de los contextos de listas (argumentos de funciones y plantillas, listas de inicializadores, etc.). Los paréntesis de la forma a, (b, c), d en dichos contextos pueden habilitar al operador de coma en comparación con la forma regular a, b, c, d donde no se aplica el operador de coma.

5.18 Operador de coma [expr.comma]

2 En contextos donde a coma se le da un significado especial, [Ejemplo: en listas de argumentos a funciones (5.2.2) y listas de inicializadores (8.5) -ejemplo] el operador de coma como se describe en la cláusula 5 puede aparecer solo entre paréntesis. [Ejemplo:

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

tiene tres argumentos, el segundo de los cuales tiene el valor 5. - fin del ejemplo]

Resolución de ambigüedad de análisis molestos

La compatibilidad con versiones anteriores de C y su sintaxis de declaración de funciones arcanas puede llevar a sorprendentes ambigüedades de análisis, conocidas como análisis molestos. Esencialmente, todo lo que se pueda analizar como una declaración se analizará como uno solo , aunque también se aplicaría un análisis competitivo.

6.8 Resolución de ambigüedad [stmt.ambig]

1 Hay una ambigüedad en la gramática que involucra expresiones-declaraciones y declaraciones : una expresión-declaración con una conversión de tipo explícita de tipo de función (5.2.3) ya que su subexpresión más a la izquierda puede ser indistinguible de una declaración donde el primer declarador comienza con a ( . En esos casos, 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 molde 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 un molde de estilo de función como el inicializador. Al igual que para las ambigüedades mencionadas en 6.8, la resolución es considerar una construcción que podría ser una declaración una declaración . [Nota: Una declaración puede ser desambiguada explícitamente mediante un molde no funcional, mediante un signo = para indicar la inicialización o eliminando los paréntesis redundantes alrededor del nombre del parámetro. -finalizar] [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
}

-Final ejemplo]

Un ejemplo famoso de esto es el Pars más vengativo , 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 devolución es list<int> . La función de datos toma dos parámetros:

  • El primer parámetro se llama dataFile . Su tipo es istream_iterator<int> . Los paréntesis alrededor de dataFile son superfluos y se ignoran.
  • El segundo parámetro no tiene nombre. Su tipo es un puntero a la función que no toma nada y devuelve un istream_iterator<int> .

Colocando paréntesis adicionales alrededor del primer argumento de 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 inicialización de llaves que permite evitar dichos problemas de análisis en muchos contextos.

Deducir la referencia en expresiones decltype

A diferencia de auto deducción de tipo auto , decltype permite decltype referencia (referencias de valor l y valor r). Las reglas distinguen entre decltype(e) y decltype((e)) :

7.1.6.2 Especificadores de tipo simples [dcl.type.simple]

4 Para una expresión e , el tipo indicado por decltype(e) se define de la siguiente manera:

- si e es una expresión de id no aparente o un acceso de miembro de clase no apareado (5.2.5), decltype(e) es el tipo de la entidad nombrada por e . Si no existe tal entidad, o si e nombra un conjunto de funciones sobrecargadas, el programa está mal formado;

- de lo contrario, si e es un valor decltype(e) , decltype(e) es T&& , donde T es el tipo de e ;

- de lo contrario, si e es un valor decltype(e) , 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 de 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&

-finalizar el ejemplo] [Nota: Las reglas para determinar tipos que implican decltype(auto) se especifican en 7.1.6.4. -finalizar nota]

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 C++FAQ y 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

La primera string devuelve, la segunda devuelve la string & , que es una referencia a la variable local str .

Prevención de errores relacionados con la macro del preprocesador

Hay 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

  • usando paréntesis alrededor de parámetros de macro dentro de la definición de macro #define TIMES(A, B) (A) * (B); para evitar la precedencia del operador no deseado (por ejemplo, en TIMES(1 + 2, 2 + 1) que rinde 9 pero arrojaría 6 sin los paréntesis alrededor (A) y (B)
  • usando paréntesis alrededor de argumentos de macro que tienen comas dentro: assert((std::is_same<int, int>::value)); que de otro modo no compilaría
  • usando paréntesis alrededor de una función para proteger contra la expansión de macro en los encabezados incluidos: (min)(a, b) (con el efecto secundario no deseado de también deshabilitar ADL)

En general, en los lenguajes de programación, los paréntesis "adicionales" implican que no están cambiando el orden o significado de análisis sintáctico. Se están agregando para aclarar el orden (precedencia del operador) en beneficio de las personas que leen el código, y su único efecto sería ralentizar levemente el proceso de compilación y reducir los errores humanos para comprender el código (probablemente acelerando el proceso de desarrollo general) )

Si un conjunto de paréntesis cambia la forma en que se analiza una expresión, entonces, por definición, no son extra. Los paréntesis que convierten un análisis ilegal / no válido en uno legal no son "extra", aunque eso puede indicar un diseño de lenguaje deficiente.







c++-faq