C ++, declaración de variable en la expresión 'if'

114

¿Que está pasando aqui?

if(int a = Func1())
{
    // Works.
}

if((int a = Func1()))
{
    // Fails to compile.
}

if((int a = Func1())
    && (int b = Func2()))
)
{
    // Do stuff with a and b.
    // This is what I'd really like to be able to do.
}

La sección 6.4.3 de la norma de 2003 explica cómo las variables declaradas en una condición de declaración de selección tienen un alcance que se extiende hasta el final de las subdestaciones controladas por la condición. Pero no veo dónde dice nada sobre no poder poner paréntesis alrededor de la declaración, ni dice nada sobre una sola declaración por condición.

Esta limitación es molesta incluso en los casos en los que solo se requiere una declaración en la condición. Considera esto.

bool a = false, b = true;

if(bool x = a || b)
{

}

Si quiero ingresar el alcance 'if' -body con x establecido en falso, entonces la declaración necesita paréntesis (ya que el operador de asignación tiene menor precedencia que el OR lógico), pero dado que no se puede usar el paréntesis, requiere la declaración de x fuera el cuerpo, filtrando esa declaración a un alcance mayor del deseado. Obviamente, este ejemplo es trivial, pero un caso más realista sería aquel en el que a y b son funciones que devuelven valores que deben probarse

Entonces, ¿lo que quiero hacer es no conforme con el estándar, o mi compilador simplemente me está rompiendo las bolas (VS2008)?

Neutrino
fuente
6
"Si quiero entrar en el ciclo con" <- sus ejemplos tienen if. ifno es un bucle, es un condicional.
crashmstr
2
@crashmstr: verdadero, pero las condiciones para while son las mismas que para if.
Mike Seymour
2
¿No se puede hacer esto con el operador de coma? Es decir: if (int a = foo(), int b = bar(), a && b)? Si el operador de coma no está sobrecargado, el estándar dice que las expresiones se evalúan de izquierda a derecha y el valor del resultado es la última expresión. Funciona con forinicialización de bucles, ¿por qué no aquí?
Archie
@ Archie: Acabo de probar esto, no pude hacerlo funcionar. ¿Quizás puedas dar un ejemplo práctico?
James Johnston
@JamesJohnston: También lo he intentado y no parece funcionar. Esa idea vino de la parte superior de mi cabeza, me sugirió cómo iffunciona, y parece ser una suposición incorrecta.
Archie

Respuestas:

63

A partir de C ++ 17, lo que estaba intentando hacer finalmente es posible :

if (int a = Func1(), b = Func2(); a && b)
{
    // Do stuff with a and b.
}

Tenga en cuenta el uso de ;en lugar de ,para separar la declaración y la condición real.

fwyzard
fuente
23
¡Agradable! Siempre sospeché que estaba adelantado a mi tiempo.
Neutrino
106

Creo que ya insinuó el tema. ¿Qué debería hacer el compilador con este código?

if (!((1 == 0) && (bool a = false))) {
    // what is "a" initialized to?

El operador "&&" es un Y lógico de cortocircuito. Eso significa que si la primera parte (1==0)resulta ser falsa, entonces la segunda parte (bool a = false)no debe evaluarse porque ya se sabe que la respuesta final será falsa. Si (bool a = false)no se evalúa, ¿qué hacer más adelante con el código que se usa a? ¿No inicializaríamos la variable y la dejaríamos sin definir? ¿Lo inicializaríamos con el valor predeterminado? ¿Qué pasa si el tipo de datos es una clase y hacer esto tiene efectos secundarios indeseables? ¿Qué pasa si en lugar de boolusar una clase y no tiene un constructor predeterminado de modo que el usuario debe proporcionar parámetros? ¿Qué hacemos entonces?

Aquí hay otro ejemplo:

class Test {
public:
    // note that no default constructor is provided and user MUST
    // provide some value for parameter "p"
    Test(int p);
}

if (!((1 == 0) && (Test a = Test(5)))) {
    // now what do we do?!  what is "a" set to?

Parece que la limitación que ha encontrado parece perfectamente razonable: evita que sucedan este tipo de ambigüedades.

James Johnston
fuente
1
Buen punto. Es posible que desee mencionar el cortocircuito explícitamente, en caso de que el OP u otros no estén familiarizados con él.
Chris Cooper
7
No había pensado en eso. Aunque en el ejemplo que proporcionó, el cortocircuito evita que se ingrese el alcance de la declaración condicional, en cuyo caso la parte de la expresión que declara que la variable no se procesa no es un problema, ya que su alcance se limita al de la declaración condicional. En cuyo caso, ¿no sería mejor si el compilador generara un error solo en aquellos casos en los que existe la posibilidad de que se ingrese el alcance de la declaración condicional cuando una parte de la expresión que declara una variable no se procesa? Que no fue el caso en los ejemplos que di.
Neutrino
@Neutrino A primera vista, tu idea suena un poco como el problema SAT, que no es tan fácil de resolver, al menos en el caso general.
Christian Rau
5
Lo que explica acerca de todos los problemas de tener múltiples declaraciones de variables en la condición if y el hecho de que solo puede usarlas de manera restringida me hace preguntarme por qué diablos se introdujo este tipo de declaración en primer lugar. Nunca había nunca sintió la necesidad de sintaxis tal antes de verlo en algún ejemplo de código. Encuentro esta sintaxis bastante torpe y creo que declarar la variable antes del bloque if es mucho más legible. Si realmente necesita restringir el alcance de esa variable, puede poner un bloque adicional alrededor del bloque if. Nunca he usado esta sintaxis.
Giorgio
2
Personalmente, creo que sería bastante elegante poder restringir el alcance de las variables que está utilizando exactamente al alcance del bloque de instrucciones que necesita usarlas, sin tener que recurrir a medidas desagradables como llaves de alcance anidadas adicionales.
Neutrino
96

La condición en una declaración ifo whilepuede ser una expresión o una declaración de variable única (con inicialización).

Su segundo y tercer ejemplo no son expresiones válidas ni declaraciones válidas, ya que una declaración no puede formar parte de una expresión. Si bien sería útil poder escribir código como su tercer ejemplo, requeriría un cambio significativo en la sintaxis del lenguaje.

No veo dónde dice nada sobre no poder poner paréntesis alrededor de la declaración, ni dice nada sobre solo una declaración por condición.

La especificación de sintaxis en 6.4 / 1 da lo siguiente para la condición:

condition:
    expression
    type-specifier-seq declarator = assignment-expression

especificando una sola declaración, sin paréntesis ni otros adornos.

Mike Seymour
fuente
3
¿Hay alguna razón o trasfondo para esto?
Tomáš Zato - Reincorporación a Monica
23

Si desea incluir variables en un ámbito más estrecho, siempre puede utilizar adicionales { }

//just use { and }
{
    bool a = false, b = true;

    if(bool x = a || b)
    {
        //...
    }
}//a and b are out of scope
crashmstr
fuente
5
+1. Además, movería la declaración de x al bloque circundante: ¿por qué debería tener un estado especial wrt ay b?
Giorgio
1
Obvio, pero no convincente: lo mismo podría decirse de las variables de ciclo ordinarias. (Por supuesto, la necesidad de un alcance variable limitado es mucho más común en los bucles)
Peter - Reincorpora a Monica
18

La última sección ya funciona, solo tienes que escribirla ligeramente diferente:

if (int a = Func1())
{
   if (int b = Func2())
   {
        // do stuff with a and b
   }
}
Bo Persson
fuente
2

Aquí hay una solución fea usando un bucle (si ambas variables son enteros):

#include <iostream>

int func1()
{
    return 4;
}

int func2()
{
    return 23;
}

int main()
{
    for (int a = func1(), b = func2(), i = 0;
        i == 0 && a && b; i++)
    {
        std::cout << "a = " << a << std::endl;
        std::cout << "b = " << b << std::endl;
    }

    return 0;
}

Pero esto confundirá a otros programadores y es un código bastante malo, por lo que no se recomienda.

Un {}bloque envolvente simple (como ya se recomendó) es mucho más fácil de leer:

{
    int a = func1();
    int b = func2();

    if (a && b)
    {
        std::cout << "a = " << a << std::endl;
        std::cout << "b = " << b << std::endl;
    }
}
básico6
fuente
1

Una cosa a tener en cuenta, también es que las expresiones dentro del bloque if más grande

if (!((1 == 0) && (bool a = false)))

no están necesariamente garantizados para ser evaluados de izquierda a derecha. Un error bastante sutil que tuve en su día tenía que ver con el hecho de que el compilador en realidad estaba probando de derecha a izquierda en lugar de de izquierda a derecha.

DukeBrymin
fuente
5
Hoy en día, sin embargo, C99 exige que && y || se evalúan de izquierda a derecha.
b0fh
2
Creo que la evaluación de derecha a izquierda de los argumentos nunca fue posible debido a la lógica de cortocircuito. Siempre se usó para cosas como una prueba de puntero y puntero en una sola expresión, como if(p && p->str && *p->str) .... De derecha a izquierda habría sido mortal y no sutil. Con otros operadores, ¡incluso asignación! - y argumentos de llamada de función, tiene razón, pero no con los operadores de cortocircuito.
Peter - Reincorpora a Monica
1

Con un poco de magia de plantilla, puede evitar el problema de no poder declarar múltiples variables:

#include <stdio.h>

template <class LHS, class RHS>
struct And_t {
  LHS lhs;
  RHS rhs;

  operator bool () {
    bool b_lhs(lhs);
    bool b_rhs(rhs);
    return b_lhs && b_rhs;
  }
};
template <class LHS, class RHS> 
And_t<LHS, RHS> And(const LHS& lhs, const RHS& rhs) { return {lhs, rhs}; }

template <class LHS, class RHS>
struct Or_t {
LHS lhs;
RHS rhs;

  operator bool () {
    bool b_lhs(lhs);
    bool b_rhs(rhs);
    return b_lhs || b_rhs;
  }
};
template <class LHS, class RHS> 
Or_t<LHS, RHS> Or(const LHS& lhs, const RHS& rhs) { return {lhs, rhs}; }

int main() {
  if (auto i = And(1, Or(0, 3))) {
    printf("%d %d %d\n", i.lhs, i.rhs.lhs, i.rhs.rhs);
  }
  return 0;
}

(Tenga en cuenta que esto pierde la evaluación de cortocircuito).

BCS
fuente
Supongo que pierde (¿o se afloja?)
Peter - Reincorporar a Monica
5
Creo que la intención de OP era la brevedad, la claridad y la facilidad de mantenimiento del código. La "solución" que propuso hace lo contrario.
Dženan