C ++, Variablendeklaration im 'if'-Ausdruck

113

Was ist denn hier los?

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.
}

In Abschnitt 6.4.3 des Standards von 2003 wird erläutert, wie Variablen, die in einer Auswahlanweisungsbedingung deklariert sind, einen Bereich haben, der sich bis zum Ende der von der Bedingung gesteuerten Unteranweisungen erstreckt. Aber ich sehe nicht, wo es etwas darüber aussagt, dass die Deklaration nicht in Klammern gesetzt werden kann, und es sagt auch nichts über nur eine Deklaration pro Bedingung aus.

Diese Einschränkung ist selbst in Fällen ärgerlich, in denen nur eine Erklärung in der Bedingung erforderlich ist. Bedenken Sie.

bool a = false, b = true;

if(bool x = a || b)
{

}

Wenn ich den 'if "-Körperbereich eingeben möchte, wobei x auf false gesetzt ist, benötigt die Deklaration Klammern (da der Zuweisungsoperator eine niedrigere Priorität als das logische ODER hat), aber da Klammern nicht verwendet werden können, muss x außerhalb deklariert werden Der Körper verliert diese Deklaration an einen größeren Umfang als gewünscht. Offensichtlich ist dieses Beispiel trivial, aber ein realistischerer Fall wäre einer, bei dem a und b Funktionen sind, die Werte zurückgeben, die getestet werden müssen

Entspricht das, was ich tun möchte, nicht dem Standard, oder sprengt mein Compiler nur meine Bälle (VS2008)?

Neutrino
quelle
6
"Wenn ich mit" <in die Schleife eintreten möchte - haben Ihre Beispiele if. ifist keine Schleife, es ist eine Bedingung.
Crashmstr
2
@crashmstr: stimmt, aber die Bedingungen für while sind die gleichen wie für if.
Mike Seymour
2
Kann das nicht mit dem Komma-Operator gemacht werden? Ich meine : if (int a = foo(), int b = bar(), a && b)? Wenn der Kommaoperator nicht überladen ist, gibt der Standard an, dass die Ausdrücke von links nach rechts ausgewertet werden und der Ergebniswert der letzte Ausdruck ist. Es funktioniert mit forSchleifeninitialisierung, warum nicht hier?
Archie
@Archie: Ich habe es gerade versucht, ich konnte es nicht zum Laufen bringen. Vielleicht können Sie ein funktionierendes Beispiel liefern?
James Johnston
@ JamesJohnston: Ich habe es gerade auch versucht und es scheint nicht zu funktionieren. Diese Idee kam gerade von oben, ich wurde von der Funktionsweise vorgeschlagen if, und es scheint eine falsche Annahme zu sein.
Archie

Antworten:

63

Ab C ++ 17 ist endlich möglich, was Sie versucht haben :

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

Beachten Sie die Verwendung von ;von anstelle von ,, um die Deklaration und den tatsächlichen Zustand zu trennen.

fwyzard
quelle
23
Nett! Ich hatte immer den Verdacht, meiner Zeit voraus zu sein.
Neutrino
106

Ich denke, Sie haben das Problem bereits angedeutet. Was soll der Compiler mit diesem Code machen?

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

Der Operator "&&" ist ein logisches UND-Kurzschluss-UND. Das heißt, wenn sich der erste Teil (1==0)als falsch herausstellt, sollte der zweite Teil (bool a = false)nicht bewertet werden, da bereits bekannt ist, dass die endgültige Antwort falsch sein wird. Wenn dies (bool a = false)nicht ausgewertet wird, was ist dann mit dem später verwendeten Code zu tun a? Würden wir die Variable einfach nicht initialisieren und undefiniert lassen? Würden wir es auf die Standardeinstellung initialisieren? Was wäre, wenn der Datentyp eine Klasse wäre und dies unerwünschte Nebenwirkungen hätte? Was wäre, wenn anstelle von boolIhnen eine Klasse verwendet würde und sie keinen Standardkonstruktor hätte, sodass der Benutzer Parameter angeben muss - was machen wir dann?

Hier ist ein weiteres Beispiel:

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?

Die Einschränkung, die Sie gefunden haben, scheint völlig vernünftig zu sein - sie verhindert, dass solche Unklarheiten auftreten.

James Johnston
quelle
1
Guter Punkt. Sie können den Kurzschluss explizit erwähnen, falls das OP oder andere nicht damit vertraut sind.
Chris Cooper
7
Daran hatte ich nicht gedacht. Obwohl in dem von Ihnen angegebenen Beispiel der Kurzschluss die Eingabe des Bereichs für bedingte Anweisungen verhindert, ist in diesem Fall der Teil des Ausdrucks, der deklariert, dass die Variable nicht verarbeitet wird, kein Problem, da sein Bereich auf den der bedingten Anweisung beschränkt ist. In welchem ​​Fall wäre es nicht besser, wenn der Compiler einen Fehler nur in den Fällen auslösen würde, in denen die Möglichkeit besteht, dass der Bereich für bedingte Anweisungen eingegeben wird, wenn ein Teil des Ausdrucks, der eine Variable deklariert hat, nicht verarbeitet wurde? Was in den Beispielen, die ich gegeben habe, nicht der Fall war.
Neutrino
@Neutrino Auf den ersten Blick klingt Ihre Idee ein bisschen wie das SAT-Problem, das zumindest im allgemeinen Fall nicht so einfach zu lösen ist.
Christian Rau
5
Was Sie über all die Probleme mit mehreren Variablendeklarationen in der if-Bedingung erklären und die Tatsache, dass Sie sie nur eingeschränkt verwenden können, lässt mich fragen, warum in aller Welt diese Art von Deklaration überhaupt eingeführt wurde. Ich hatte noch nie jemals die Notwendigkeit einer solchen eine Syntax fühlte , bevor ich es in einigen Codebeispiel sah. Ich finde diese Syntax ziemlich ungeschickt und denke, dass das Deklarieren der Variablen vor dem if-Block viel besser lesbar ist. Wenn Sie den Umfang dieser Variablen wirklich einschränken müssen, können Sie einen zusätzlichen Block um den if-Block setzen. Ich habe diese Syntax nie verwendet.
Giorgio
2
Persönlich halte ich es für ziemlich elegant, den Umfang der von Ihnen verwendeten Variablen auf genau den Umfang des Anweisungsblocks beschränken zu können, der sie verwenden muss, ohne auf hässliche Maßnahmen wie extra verschachtelte Scoping-Klammern zurückgreifen zu müssen.
Neutrino
96

Der Zustand in einer ifoder whileAnweisung kann entweder eine sein , die Expression oder eine einzelne variable Deklaration (mit Initialisierung).

Ihr zweites und drittes Beispiel sind weder gültige Ausdrücke noch gültige Deklarationen, da eine Deklaration nicht Teil eines Ausdrucks sein kann. Es wäre zwar nützlich, Code wie in Ihrem dritten Beispiel schreiben zu können, es würde jedoch eine wesentliche Änderung der Sprachsyntax erforderlich sein.

Ich sehe nicht, wo es etwas darüber aussagt, dass die Deklaration nicht in Klammern gesetzt werden kann, und es sagt auch nichts über nur eine Deklaration pro Bedingung aus.

Die Syntaxspezifikation in 6.4 / 1 gibt für die Bedingung Folgendes an:

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

Angabe einer einzelnen Erklärung ohne Klammern oder andere Verzierungen.

Mike Seymour
quelle
3
Gibt es einen Grund oder Hintergrund dafür?
Tomáš Zato - Wiedereinsetzung Monica
23

Wenn Sie Variablen in einem engeren Bereich einschließen möchten, können Sie immer zusätzliche verwenden { }

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

    if(bool x = a || b)
    {
        //...
    }
}//a and b are out of scope
Crashmstr
quelle
5
+1. Außerdem würde ich die Deklaration von x in den umgebenden Block verschieben: Warum sollte sie einen Sonderstatus für a und b haben?
Giorgio
1
Offensichtlich, aber nicht überzeugend: Gleiches gilt für gewöhnliche Schleifenvariablen. (Zugegeben, die Notwendigkeit eines begrenzten variablen Bereichs ist in Schleifen viel häufiger.)
Peter - Reinstate Monica
18

Der letzte Abschnitt funktioniert bereits, Sie müssen ihn nur etwas anders schreiben:

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

Hier ist eine hässliche Problemumgehung mithilfe einer Schleife (wenn beide Variablen Ganzzahlen sind):

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

Aber das wird andere Programmierer verwirren und es ist ziemlich schlechter Code, also nicht zu empfehlen.

Ein einfacher umschließender {}Block (wie bereits empfohlen) ist viel einfacher zu lesen:

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

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

Eine Sache zu beachten ist auch, dass die Ausdrücke innerhalb des größeren if-Blocks

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

Es ist nicht unbedingt garantiert, dass sie von links nach rechts bewertet werden. Ein ziemlich subtiler Fehler, den ich damals hatte, hatte damit zu tun, dass der Compiler tatsächlich von rechts nach links statt von links nach rechts testete.

DukeBrymin
quelle
5
Heutzutage schreibt C99 jedoch vor, dass && und || werden von links nach rechts ausgewertet.
b0fh
2
Ich denke, eine Bewertung der Argumente von rechts nach links war aufgrund der Kurzschlusslogik nie möglich. Es wurde immer für Dinge wie einen Test für Zeiger und Spitzen in einem einzelnen Ausdruck verwendet, wie z if(p && p->str && *p->str) .... Von rechts nach links wäre tödlich und nicht subtil gewesen. Mit anderen Betreibern - auch Zuordnung! - und Funktionsaufrufargumente Sie haben Recht, aber nicht mit den Kurzschlussoperatoren.
Peter - Reinstate Monica
1

Mit ein wenig Template-Magie können Sie das Problem umgehen, dass Sie nicht mehrere Variablen deklarieren können:

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

(Beachten Sie, dass dadurch die Kurzschlussauswertung verloren geht.)

BCS
quelle
Ich nehme an, es verliert (oder löst sich?)
Peter - Reinstate Monica
5
Ich denke, dass die Absicht von OP die Kürze, Klarheit und Wartbarkeit des Codes war. Sie haben vorgeschlagen, dass "Lösung" das Gegenteil bewirkt.
Dženan