C ++, dichiarazione di variabile nell'espressione "if"


114

Cosa sta succedendo qui?

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 sezione 6.4.3 dello standard del 2003 spiega come le variabili dichiarate in una condizione dell'istruzione di selezione abbiano un ambito che si estende alla fine delle sottostazioni controllate dalla condizione. Ma non vedo dove dice qualcosa sul non essere in grado di mettere parentesi intorno alla dichiarazione, né dice nulla su una sola dichiarazione per condizione.

Questa limitazione è fastidiosa anche nei casi in cui è richiesta una sola dichiarazione nella condizione. Considera questo.

bool a = false, b = true;

if(bool x = a || b)
{

}

Se voglio inserire l'ambito del corpo 'if' con x impostato su false, la dichiarazione necessita di parentesi (poiché l'operatore di assegnazione ha una precedenza inferiore all'OR logico), ma poiché le parentesi non possono essere utilizzate, richiede la dichiarazione di x all'esterno il corpo, facendo trapelare quella dichiarazione in un ambito più ampio di quanto si desideri. Ovviamente questo esempio è banale ma un caso più realistico sarebbe quello in cui aeb sono funzioni che restituiscono valori che devono essere testati

Quindi quello che voglio fare non è conforme allo standard, o il mio compilatore mi sta solo rompendo le palle (VS2008)?


6
"Se voglio entrare nel ciclo con" <- i tuoi esempi l'hanno fatto if. ifnon è un ciclo, è un condizionale.
crashmstr

2
@crashmstr: true, ma le condizioni per while sono le stesse di if.
Mike Seymour

2
Non è possibile farlo con l'operatore virgola? Voglio dire if (int a = foo(), int b = bar(), a && b):? Se l'operatore virgola non è sovraccarico, lo standard dice che le espressioni vengono valutate da sinistra a destra e il valore del risultato è l'ultima espressione. Funziona con l' forinizializzazione dei loop, perché non qui?
Archie

@Archie: l'ho appena provato, non sono riuscito a farlo funzionare. Forse puoi fornire un esempio funzionante?
James Johnston

@ JamesJohnston: ho anche provato e non sembra funzionare. Quell'idea mi è appena venuta in testa, mi è stato suggerito da come iffunziona, e sembra essere un'ipotesi sbagliata.
Archie

Risposte:


63

A partire da C ++ 17 quello che stavi cercando di fare è finalmente possibile :

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

Nota l'uso di ; invece di ,per separare la dichiarazione e la condizione effettiva.


23
Bello! Ho sempre sospettato di essere in anticipo sui tempi.
Neutrino

106

Penso che tu abbia già accennato al problema. Cosa dovrebbe fare il compilatore con questo codice?

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

L'operatore "&&" è un AND logico di cortocircuito. Ciò significa che se la prima parte (1==0)risulta essere falsa, allora la seconda parte non (bool a = false)dovrebbe essere valutata perché è già noto che la risposta finale sarà falsa. Se (bool a = false)non viene valutato, cosa fare in seguito con il codice che utilizza a? Potremmo semplicemente non inizializzare la variabile e lasciarla indefinita? Lo inizializzeremmo al valore predefinito? E se il tipo di dati fosse una classe e questa operazione avesse effetti collaterali indesiderati? E se invece di boolusare una classe e non avesse un costruttore predefinito tale che l'utente deve fornire i parametri - cosa faremo allora?

Ecco un altro esempio:

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?

Sembra che la limitazione che hai trovato sia perfettamente ragionevole: impedisce il verificarsi di questo tipo di ambiguità.


1
Buon punto. Potresti voler menzionare esplicitamente il cortocircuito, nel caso in cui l'OP o altri non lo abbiano familiarità.
Chris Cooper

7
Non ci avevo pensato. Sebbene nell'esempio fornito il cortocircuito impedisca l'immissione dell'ambito dell'istruzione condizionale, nel qual caso la parte dell'espressione che dichiara la variabile non in elaborazione non è un problema, poiché il suo ambito è limitato a quello dell'istruzione condizionale. In tal caso non sarebbe meglio se il compilatore avesse generato un errore solo nei casi in cui esiste la possibilità che l'ambito dell'istruzione condizionale venga inserito quando una parte dell'espressione che dichiara una variabile non è stata elaborata? Che non era il caso negli esempi che ho fornito.
Neutrino

@ Neutrino A prima vista l'idea suona un po 'come il problema SAT, che non è così facile da risolvere, almeno nel caso generale.
Christian Rau

5
Quello che spieghi su tutti i problemi con l'avere più dichiarazioni di variabili nella condizione if e il fatto che puoi usarle solo in modo limitato mi fa chiedere perché diavolo questo tipo di dichiarazione sia stato introdotto in primo luogo. Non avevo mai mai sentito la necessità di tale sintassi un prima ho visto in qualche esempio di codice. Trovo questa sintassi piuttosto goffa e penso che dichiarare la variabile prima del blocco if sia molto più leggibile. Se hai davvero bisogno di limitare l'ambito di quella variabile, puoi inserire un blocco aggiuntivo attorno al blocco if. Non ho mai usato questa sintassi.
Giorgio

2
Personalmente penso che sarebbe piuttosto elegante essere in grado di limitare l'ambito delle variabili che stai utilizzando esattamente allo scopo del blocco di istruzioni che deve usarle, senza dover ricorrere a misure brutte come parentesi graffe di scoping annidate extra.
Neutrino

96

La condizione in un'istruzione ifo whilepuò essere un'espressione o una singola dichiarazione di variabile (con inizializzazione).

Il secondo e il terzo esempio non sono né espressioni valide, né dichiarazioni valide, poiché una dichiarazione non può far parte di un'espressione. Sebbene sarebbe utile essere in grado di scrivere codice come il tuo terzo esempio, richiederebbe una modifica significativa alla sintassi del linguaggio.

Non vedo dove dice qualcosa sul non essere in grado di mettere parentesi intorno alla dichiarazione, né dice nulla su una sola dichiarazione per condizione.

La specifica della sintassi in 6.4 / 1 fornisce quanto segue per la condizione:

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

specificando una singola dichiarazione, senza parentesi o altri ornamenti.


3
C'è qualche motivo o sfondo per questo?
Tomáš Zato - Ripristina Monica il

23

Se desideri racchiudere le variabili in un ambito più ristretto, puoi sempre utilizzare additional { }

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

    if(bool x = a || b)
    {
        //...
    }
}//a and b are out of scope

5
+1. Inoltre, sposterei la dichiarazione di x nel blocco circostante: perché dovrebbe avere uno stato speciale rispetto a aeb?
Giorgio

1
Ovvio, ma non convincente: lo stesso si potrebbe dire per le normali variabili di ciclo. (Certo, la necessità di un ambito variabile limitato è molto più comune nei loop.)
Peter - Reinstate Monica

18

L'ultima sezione funziona già, devi solo scriverla leggermente diversa:

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

2

Ecco una brutta soluzione alternativa usando un ciclo (se entrambe le variabili sono numeri interi):

#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;
}

Ma questo confonderà gli altri programmatori ed è un codice piuttosto scadente, quindi non consigliato.

Un semplice {}blocco di chiusura (come già consigliato) è molto più facile da leggere:

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

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

1

Una cosa da notare è anche che le espressioni all'interno del blocco if più grande

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

non sono necessariamente garantiti per essere valutati in modo da sinistra a destra. Un bug piuttosto sottile che avevo in passato aveva a che fare con il fatto che il compilatore stava effettivamente testando da destra a sinistra invece che da sinistra a destra.


5
Al giorno d'oggi, tuttavia, C99 impone che && e || vengono valutati da sinistra a destra.
b0fh

2
Penso che la valutazione da destra a sinistra degli argomenti non sia mai stata possibile a causa della logica del cortocircuito. È sempre stato usato per cose come un test per il puntatore e le punte in una singola espressione, come if(p && p->str && *p->str) .... Da destra a sinistra sarebbe stato mortale e non sottile. Con altri operatori - anche incarico! - e gli argomenti della chiamata di funzione hai ragione, ma non con gli operatori di cortocircuito.
Peter - Ripristina Monica il

1

Con un po 'di magia dei modelli puoi aggirare il problema di non essere in grado di dichiarare più variabili:

#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;
}

(Nota, questo perde la valutazione del cortocircuito.)


Suppongo che perda (o si allenti?)
Peter - Reinstate Monica

5
Penso che l'intento di OP fosse brevità, chiarezza e manutenibilità del codice. Hai proposto "soluzione" fa il contrario.
Dženan
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.