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节解释了在选择语句条件中声明的变量如何具有扩展到该条件所控制的子语句末尾的范围。但是我看不到在哪里说不能在声明周围加上括号,也没有说每个条件只声明一个。

即使在条件中仅需要一个声明的情况下,此限制也很麻烦。考虑一下。

bool a = false, b = true;

if(bool x = a || b)
{

}

如果要输入x设置为false的'if'-body范围,则声明需要括号(因为赋值运算符的优先级低于逻辑OR),但是由于不能使用括号,因此需要在外部声明x显然,此示例是微不足道的,但更现实的情况是其中a和b是返回需要测试的值的函数

那么我想做的是不符合该标准的事情,还是我的编译器破坏了我的工作(VS2008)?


6
“如果我想通过以下方式进入循环” <-您的示例具有ifif不是循环,而是有条件的。
crashmstr 2011年

2
@crashmstr:是,但条件while 与相同if
Mike Seymour

2
逗号运算符不能完成此操作吗?我的意思是:if (int a = foo(), int b = bar(), a && b)?如果逗号运算符未重载,则标准会说表达式是从左到右计算的,结果值是最后一个表达式。它与for循环初始化一起使用,为什么不在这里?
阿奇

@Archie:我只是尝试了一下,所以无法正常工作。也许您可以提供一个可行的例子?
詹姆士·约翰斯顿

@JamesJohnston:我也尝试过,但似乎没有用。这个想法只是从我的脑海中浮现出来的if,有人向我暗示了它是如何工作的,这似乎是错误的假设。
阿奇

Answers:


63

从C ++ 17开始,您尝试做的事情终于可以实现

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

请注意,使用of ;代替,将声明和实际条件分开。


23
真好!我一直怀疑自己在超前。
中微子

106

我认为您已经暗示了这个问题。编译器应使用此代码做什么?

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

“ &&”运算符是短路逻辑与。这意味着,如果第一部分被(1==0)证明是错误的,则(bool a = false)不应评估第二部分,因为已经知道最终答案将是错误的。如果(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或其他人不熟悉它。
克里斯·库珀

7
我没想到。尽管在示例中您提供了短路防止输入条件语句范围,但在这种情况下,声明变量未处理的表达式部分不是问题,因为它的范围限于条件语句的范围。在那种情况下,仅当编译器仅在存在部分声明变量的表达式未处理的情况下才输入条件语句范围的情况下才引发错误,这会更好吗?在我所举的例子中情况并非如此。
中微子

@Neutrino乍一看,您的想法听起来有点像SAT问题,至少在一般情况下,解决起来并不容易。
Christian Rau

5
关于if条件中具有多个变量声明的所有问题的解释,以及只能以受限方式使用它们的事实,使我感到奇怪,为什么首先要引入这种声明?我从来没有觉得这样的语法需要之前,我看到了一些代码示例。我发现此语法相当笨拙,并且我认为在if块之前声明变量更易读。如果确实需要限制该变量的范围,则可以在if块周围放置一个额外的块。我从未使用过这种语法。
乔治

2
我个人认为,能够将要使用的变量的范围限制在需要使用它们的语句块的范围之内,而不必采取诸如多余的嵌套作用域括号之类的丑陋措施,这将是相当优雅的。
中微子

96

ifwhile语句中的条件可以是expression或单个变量声明(带有初始化)。

您的第二个和第三个示例既不是有效的表达式,也不是有效的声明,因为声明不能构成表达式的一部分。虽然像第三个示例一样能够写代码将很有用,但它需要对语言语法进行重大更改。

我看不到它在哪里说不能在声明周围加上括号,也没有说每个条件只声明一个。

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都应具有特殊的状态?
乔治

1
显而易见,但不令人信服:对于普通循环变量也可以这样说。(当然,对有限变量范围的需求在循环中更为普遍。)
彼得-恢复莫妮卡(Monica

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

还需要注意的一件事是,较大的if块中的表达式

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

不一定保证以从左到右的方式进行评估。我今天遇到的一个相当微妙的错误与编译器实际上是从右到左而不是从左到右进行测试有关。


5
但是,如今,C99要求&&和|| 从左到右进行评估。
13年

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

(请注意,这会使短路评估松动。)


我想它会丢失(或变松?)
彼得-恢复莫妮卡

5
我认为OP的意图是代码简洁,清晰和可维护性。您提出的“解决方案”则相反。
Dženan
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.