【问题标题】:C++, variable declaration in 'if' expressionC ++,'if'表达式中的变量声明
【发布时间】:2011-12-11 19:40:30
【问题描述】:

这是怎么回事?

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

【问题讨论】:

  • "如果我想进入循环" if。 if 不是循环,而是条件。
  • @crashmstr: true,但while 的条件与if 相同。
  • 这不能用逗号操作符来完成吗?我的意思是:if (int a = foo(), int b = bar(), a && b)?如果逗号运算符未重载,则标准规定表达式从左到右计算,结果值为最后一个表达式。它适用于 for 循环初始化,为什么不在这里?
  • @Archie:我刚试过这个,我无法让它工作。也许您可以提供一个工作示例?
  • @JamesJohnston:我也试过了,但似乎没有用。这个想法刚刚从我的脑海中浮现,if 的工作原理向我提出了建议,这似乎是错误的假设。

标签: c++ compilation if-statement variable-declaration


【解决方案1】:

我想你已经暗示了这个问题。编译器应该如何处理这段代码?

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?

您发现的限制似乎完全合理 - 它可以防止发生此类歧义。

【讨论】:

  • 好点。您可能想明确提及短路,以防 OP 或其他人不熟悉它。
  • 我没想到。尽管在您提供的示例中,短路会阻止输入条件语句范围,但在这种情况下,声明变量未被处理的表达式部分不是问题,因为它的范围仅限于条件语句的范围。在这种情况下,如果编译器仅在那些声明变量的表达式的一部分未被处理时可能进入条件语句范围的情况下引发错误,那不是更好吗?在我给出的示例中,情况并非如此。
  • @Neutrino 乍一看,你的想法听起来有点像 SAT 问题,至少在一般情况下不是那么容易解决。
  • 您解释了在 if 条件中使用多个变量声明的所有问题以及您只能以受限方式使用它们的事实,这让我想知道为什么在地球上引入了这种声明第一名。在我在一些代码示例中看到它之前,我从来没有曾经觉得需要这种语法。我发现这种语法相当笨拙,我认为在 if 块之前声明变量更具可读性。如果您确实需要限制该变量的范围,您可以在 if 块周围放置一个额外的块。我从来没有使用过这种语法。
  • 我个人认为能够将您正在使用的变量的范围限制在需要使用它们的语句块的范围内,而不必诉诸丑陋,这将是相当优雅的措施,如额外的嵌套范围大括号。
【解决方案2】:

从 C++17 开始,您尝试执行的操作 is finally possible

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

注意使用; 代替, 来分隔声明和实际条件。

【讨论】:

  • 不错!我一直怀疑自己走在了时代的前面。
  • 现在只需要支持while循环即可。
  • @ZeroPhase "for" 带有空增量字段的语句已经提供了这个结构
  • 如果a 为零(短路行为),是否调用Func2()
  • 从我阅读的文档来看,是的:首先完成所有初始化,然后检查条件。
【解决方案3】:

ifwhile 语句中的条件可以是表达式,也可以是单个变量声明(带有初始化)。

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

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

6.4/1 中的语法规范给出了以下条件:

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

指定单个声明,不带括号或其他修饰。

【讨论】:

  • 这有什么原因或背景吗?
【解决方案4】:

如果你想将变量包含在一个更窄的范围内,你总是可以使用额外的{ }

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

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

【讨论】:

  • +1。另外,我会将 x 的声明移到周围的块中:为什么它应该具有 a 和 b 的特殊状态?
  • 显而易见,但不令人信服:对于普通循环变量也可以这样说。 (当然,有限变量范围的需求在循环中更为常见。)
【解决方案5】:

最后一部分已经可以了,你只需要写一点不同:

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

【讨论】:

    【解决方案6】:

    这是一个使用循环的丑陋解决方法(如果两个变量都是整数):

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

    【讨论】:

      【解决方案7】:

      需要注意的一点,也是较大的 if 块中的表达式

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

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

      【讨论】:

      • 然而,如今,C99 要求 && 和 ||从左到右进行评估。
      • 我认为由于短路逻辑,从右到左评估参数是不可能的。它总是用于测试单个表达式中的指针和指针,例如if(p &amp;&amp; p-&gt;str &amp;&amp; *p-&gt;str) ...。从右到左将是致命的,而不是微妙的。与其他运算符 - 甚至赋值! -- 和函数调用参数你是对的,但不是短路运算符。
      【解决方案8】:

      通过一点模板魔法,您可以解决无法声明多个变量的问题:

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

      (注意,这会丢失短路评估。)

      【讨论】:

      • 我想它(或松动?)
      • 我认为 OP 的意图是代码简洁、清晰和可维护。您提出的“解决方案”恰恰相反。
      猜你喜欢
      • 2011-12-12
      • 2013-05-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-08
      相关资源
      最近更新 更多