C ++、「if」式での変数宣言


113

何が起きてる?

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

2003年標準のセクション6.4.3は、選択ステートメント条件で宣言された変数が、条件によって制御されるサブステートメントの終わりまで及ぶスコープを持つ方法を説明しています。しかし、宣言を括弧で囲まないことについて何が書かれているか、条件ごとに1つの宣言のみについて何も書かれていないことはわかりません。

この制限は、条件の宣言が1つだけ必要な場合でも厄介です。このことを考慮。

bool a = false, b = true;

if(bool x = a || b)
{

}

xをfalseに設定して 'if "-bodyスコープを入力する場合、宣言には括弧が必要です(代入演算子の優先順位は論理ORよりも低いため)が、括弧は使用できないため、外部でxを宣言する必要があります本体が、その宣言を必要以上のスコープにリークしていることは明らかです。明らかにこの例は取るに足らないものですが、より現実的なケースは、aとbがテストする必要のある値を返す関数である場合です。

それで、私は標準に準拠しないことをしたいのですか、それとも私のコンパイラは私のボールを破壊しているだけですか(VS2008)?


6
「もしループに入りたいなら」<-あなたの例にはがありifます。ifループではなく、条件付きです。
crashmstr

2
@crashmstr:true、ただしの条件while はと同じですif
Mike Seymour

2
これはコンマ演算子で行うことはできませんか?つまり:if (int a = foo(), int b = bar(), a && b)?カンマ演算子がオーバーロードされていない場合、標準では式は左から右に評価され、結果の値は最後の式になります。forループの初期化で動作しますが、ここではどうですか?
Archie、

@Archie:私はこれを試してみましたが、動作させることができませんでした。多分あなたは実用的な例を提供できますか?
James Johnston、

@JamesJohnston:私も試したところ、うまくいかないようです。その考えは頭のてっぺんから生まれましたif
Archie

回答:


63

C ++ 17の時点で、やろうとしていたことが最終的に可能になりました

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

;の代わりにの使用に注意,して、宣言と実際の条件を分離してください。


23
いいね!私はいつも自分が時代を先取りしていると思っていました。
ニュートリノ2017年

106

あなたはすでにこの問題をほのめかしたと思います。コンパイラはこのコードで何をすべきですか?

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

「&&」演算子は、短絡論理ANDです。つまり、最初の部分(1==0)が偽であることが判明した場合(bool a = false)、最終的な答えが偽であることがすでにわかっているため、2番目の部分は評価されません。(bool a = false)が評価されていない場合は、後で使用するコードで何を行うのaですか?変数を初期化せず、未定義のままにしますか?それをデフォルトに初期化しますか?データ型がクラスであり、これを行うと望ましくない副作用があった場合はどうなりますか?boolクラスを使用する代わりに、ユーザーパラメーターを指定する必要があるようなデフォルトのコンストラクターがない場合はどうなるでしょうか。

次に別の例を示します。

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?

あなたが見つけた制限は完全に合理的であるように思えます-それはこれらの種類のあいまいさが起こるのを防ぎます。


1
いい視点ね。OPや他の人が慣れていない場合に備えて、短絡について明示的に言及することをお勧めします。
Chris Cooper

7
私はそのことを考えていませんでした。例では短絡を指定したため、条件ステートメントのスコープに入ることができませんが、その場合、変数のスコープが条件ステートメントのスコープに限定されるため、変数の処理中でないことを宣言する式の部分は問題になりません。変数を宣言した式の一部が処理されなかったときに、条件ステートメントのスコープに入る可能性がある場合にのみ、コンパイラーがエラーを発生させた方がよいのではないでしょうか。私が挙げた例ではそうではありませんでした。
ニュートリノ、

@Neutrino一見すると、あなたのアイデアはSAT問題のように聞こえますが、少なくとも一般的なケースでは、それほど簡単には解決できません。
Christian Rau

5
if条件で複数の変数宣言を使用する場合のすべての問題と、制限された方法でのみ使用できるという事実について説明すると、なぜこの種の宣言が最初に導入されたのか不思議に思います。コード例でそれを見るまで、そのような構文の必要性を感じたことはありませんでした。この構文は不格好であり、ifブロックの前に変数を宣言する方が読みやすいと思います。その変数のスコープを本当に制限する必要がある場合は、ifブロックの周りに追加のブロックを置くことができます。私はこの構文を使用したことがありません。
Giorgio

2
個人的には、使用する変数のスコープを、それを使用する必要のあるステートメントブロックのスコープに正確に制限でき、余分なネストされたスコープブレースのような醜い測定に頼る必要がないのは、かなりエレガントだと思います。
ニュートリノ、

96

ifor whileステートメントの条件は、、または単一変数宣言(初期化あり)のいずれかです。

宣言は式の一部を形成できないため、2番目と3番目の例は有効な式でも有効な宣言でもありません。3番目の例のようなコードを記述できると便利ですが、言語構文を大幅に変更する必要があります。

宣言をかっこで囲めないことについて何が書かれているか、条件ごとに1つの宣言のみについて何も書かれていないのはわかりません。

6.4 / 1の構文仕様では、条件について次のようになっています。

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

かっこやその他の装飾なしで単一の宣言を指定します。


3
これには理由や背景がありますか?
トマーシュZato -復活モニカ

23

変数をより狭いスコープで囲む場合は、常に追加の { }

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

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

5
+1。さらに、xの宣言を周囲のブロックに移動します。なぜaとbに関して特別なステータスを持つ必要があるのですか?
Giorgio

1
明白ですが説得力はありません。通常のループ変数についても同じことが言えます。(確かに、制限された変数スコープの必要性はループではるかに一般的です。)
ピーター-モニカを復活させる

18

最後のセクションは既に機能していますが、少し異なるように記述する必要があります。

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

2

次に、ループを使用した醜い回避策を示します(両方の変数が整数の場合)。

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

しかし、これは他のプログラマを混乱させ、それはかなり悪いコードなので、お勧めしません。

単純な囲み{}ブロック(既に推奨されている)の方がはるかに読みやすくなっています。

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

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

1

注意すべきことの1つは、より大きなifブロック内の式が

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

必ずしも左から右への評価が保証されているわけではありません。私が当時持っていたかなり微妙なバグの1つは、コンパイラーが実際には左から右ではなく右から左にテストしていたという事実に関係していました。


5
しかし最近では、C99は&&および||を義務付けています。左から右に評価されます。
b0fh 2013

2
論理の短絡のため、引数の右から左への評価は不可能だったと思います。のように、単一の式でのポインタと指示先のテストなどに常に使用されていましたif(p && p->str && *p->str) ...。右から左への移動は致命的であり、微妙ではありません。他のオペレーターの場合-割り当ても!-関数呼び出しの引数は正しいが、短絡演算子ではそうではない。
ピーター-モニカ

1

小さなテンプレートマジックを使用すると、複数の変数を宣言できないという問題をある程度回避できます。

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

(これにより、短絡評価が失われます。)


私はそれが失うと思います(または緩みますか?)
Peter-Reinstate Monica

5
OPの意図は、コードの簡潔さ、明確さ、保守性であったと思います。あなたが提案した「解決策」はその逆です。
Dženan
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.