web-dev-qa-db-ja.com

C ++、 'if'式での変数宣言

何が起きてる?

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)?

102
Neutrino

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

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

宣言の周りに括弧を入れられないことについて何が述べられているのか、条件ごとに宣言が1つだけであることについて何も言われていないのがわかりません。

6.4/1の構文仕様では、条件について次のことが規定されています。

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

単一の宣言を指定します。括弧やその他の装飾はありません。

92
Mike Seymour

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

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

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

別の例を次に示します。

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?

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

102
James Johnston

C++ 17現在、あなたがやろうとしていたこと 最終的に可能です

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

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

44
fwyzard

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

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

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

最後のセクションはすでに機能しているため、わずかに異なる記述をする必要があります。

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

ループを使用したい回避策は次のとおりです(両方の変数が整数の場合)。

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

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

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

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

1
DukeBrymin

小さなテンプレートの魔法を使えば、複数の変数を宣言できないという問題をある程度回避できます。

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

(注意、これは短絡評価を失います。)

0
BCS