【问题标题】:How to programmatically see which conditions were not met in C++ when using logical AND (&&)?使用逻辑 AND (&&) 时,如何以编程方式查看 C++ 中不满足哪些条件?
【发布时间】:2024-01-23 23:30:01
【问题描述】:

我试图有效地推断出哪些条件导致程序忽略了 if 语句,而不使用一系列 if 语句来单独验证每个变量的相对完整性。

这可能吗?

bool state = false;
int x = 0;
int y = 1;
int z = 3;

if(x == 0 && y == 1 && z == 2)    {
// Do something...
state == true;
}

if(state == false)    {

std::cout << "I did not execute the if statement because the following 
conditions were not met: " << std::endl;

/*Find a way to make the program output that z != 3 stopped the 
conditional from running without directly using if(z != 2)*/

} 

【问题讨论】:

  • 如果你想知道到底是哪一个失败了,那你为什么不分别做每个条件呢?
  • 由于您只想检查变量值,因此请使用调试器在所需位置停止程序执行并在其帮助下打印每个变量。
  • 打印else部分的所有值。我认为那会做。在运行时,您可以知道哪个值是意外的。

标签: c++ logical-operators


【解决方案1】:

您可以在if 中的每个条件之间引入一个计数器作为“条件”,以查看运算符&amp;&amp; 的短路评估何时禁止执行后一个条件:

int nrOfConditionFailing = 1;

if(x == 0 &&
   nrOfConditionFailing++ && y == 1 &&
   nrOfConditionFailing++ && z == 2)    {
    state = true;
}

if (!state) {
  cout << "failed due to condition nr " << nrOfConditionFailing << endl;
}

如果要检查所有条件,则不能在单个 if 语句中完成;如果前面的条件之一评估为假,则运算符 && 的短路评估将阻止甚至检查/评估后面的条件。

但是,您可以将检查作为一个表达式,在 unsigned int 中为每个未满足的条件标记一个位:

int x = 1;
int y = 1;
int z = 3;

unsigned int c1 = !(x == 0);
unsigned int c2 = !(y == 1);
unsigned int c3 = !(z == 2);

unsigned int failures =
  (c1 << 0)
| (c2 << 1)
| (c3 << 2);

if (failures) {
    for(int i=0; i<3; i++) {
        if (failures & (1 << i)) {
            cout << "condition " << (i+1) << " failed." << endl;
        }
    }
}
else {
  cout << "no failures." << endl;
}

【讨论】:

  • 有趣且非常有用,基本上已经确定了......有没有什么想法可以在不满足多种条件时应用类似的概念?
  • 没有机会检测单个if-statement 中是否不满足多个条件;如果前面的条件之一评估为假,则运算符 &amp;&amp; 的短路评估将阻止甚至检查/评估后面的条件。
  • 仍然是评估这些单点故障的非常好的解决方案。谢谢。
  • 单个语句中的多个 ++ 增量似乎是一种有问题的方法。从 && 语义来看是有道理的,但我想知道是否存在 UB 问题?或者只有当您在语句的计算中实际使用该值时?当然,您正在使用该值——这就是为什么您必须在开始时将 counter 变量设置为 1。
  • @DaveM。好主意,但在大多数情况下,每个&amp;&amp; 之间都有一个保证的序列点。由于这里的所有类型都是整数,++ 保证在语句的下一部分之前被评估,所以没有 UB。但是,如果&amp;&amp; 在这种情况下被重载,这不适用,那么它将是UB
【解决方案2】:

如果这是您想向最终用户显示的内容,而不仅仅是在调试时(如 cmets 中建议的那样),您可以为自己设计一个简单的数据结构。这将是一个条目列表/向量/数组,每个条目都包含 a) 一个要比较的值,b) 一个要测试的值,以及可选的 c) 测试描述。

然后简单地迭代列表,并检查它们是否相等。如果没有,您可以停止程序的流程并打印出说明。

为了更直接地回答您的问题:不,C++ 中没有任何内容可以让您检查先前语句的结果。您在源代码中看到的语句和操作会被编译,甚至可能无法在汇编指令中轻易识别。能够检查结果意味着数据必须存储在某个地方,这将极大地浪费内存和处理时间。这就是为什么你必须自己做这件事。

【讨论】:

  • 现在明白为什么这是你必须自己做的事情了。没有考虑资源。
【解决方案3】:

这可能吗?

按照您思考问题的方式是不可能的。您可以通过单独运行每个测试、存储结果然后识别其中哪些是false 来解决您的问题:

std::vector<std::tuple<std::string,bool> > tests = {
       {"x==0",x==0}, // test name as a string followed by the actual test
       {"y==1",y==1},
       {"z==2",z==2}
    };

if(!all_of(tests.begin(),tests.end(),[](std::tuple<std::string,bool> &t) { return std::get<1>(t); }))
{
   std::cout << "The following tests failed: ";
   //remove all tests that passed
   tests.erase(
        std::remove_if(tests.begin(),tests.end(),[](std::tuple<std::string,bool> &t) { return std::get<1>(t); }),
        tests.end());
   //This will only print out the tests that failed
   std::transform(tests.begin(),tests.end(),std::ostream_iterator<std::string>(std::cout, " "),[](std::tuple<std::string,bool> &t) { return std::get<0>(t); });
   std::cout << std::endl;
} else {

  //what to do if all tests were true
} 

这将评估所有测试(即,它不会使用 &amp;&amp; 的短路)并打印所有失败的测试。您可以将其封装到 class 中,以使其更具通用性和用户友好性。

【讨论】:

    【解决方案4】:

    原始代码单独测试每个变量。 &amp;&amp; 系列完全等同于一系列 if...else 语句。与另一种相比,一个没有什么效率低下的地方,使用一些棘手的解决方案来实现与简单代码相同的最终结果也没有什么“聪明”的地方。

    我可能会写:

    char const *reason = nullptr;
    
    if(x != 0)
        reason = "x failed";
    else if (y != 1)
        reason = "y failed";
    else if (z != 2 )
        reason = "z failed";
    
    if ( reason )
        std::cout << reason << '\n';
    else
    {
        // success code here...
    }
    

    【讨论】:

    • 在这种情况下的效率指向您的解决方案无法提供的即时可扩展性。
    【解决方案5】:

    我通常会执行以下操作来确定一系列有效性检查是否有效并标记哪些失败。

    unsigned long ulFlags = 0;
    int x = 0;
    int y = 1;
    int z = 3;
    
    ulFlags |= (x == 0) : 0 ? 0x0001;  // if bit set then condition failed.
    ulFlags |= (y == 1) : 0 ? 0x0002;  // if bit set then condition failed.
    ulFlags |= (z == 2) : 0 ? 0x0004;  // if bit set then condition failed.
    
    if(ulFlags == 0) {
        // Do something since all conditions are met and valid ...
    } else {
        std::cout << "I did not execute if statement because: " << std::hex << ulFlags << std::endl;
    
        /* Find a way to make the program output that z != 3 stopped the 
          conditional from running without directly using if(z != 2) */
    } 
    

    【讨论】:

      【解决方案6】:

      这与其他一些答案的想法相同,但使用模板来简化使用它的语法。将所有单独的检查存储在 std::array&lt;bool, N&gt; 和一个额外的 bool 中,以便能够重新检查完整的语句而无需再次检查单独的结果。

      没有动态分配也是一个优点。

      #include <iostream>
      #include <array>
      #include <type_traits>
      
      template <typename... N>
      struct what_failed {
          what_failed(N... n) : arr{n...}, status{(... && n)} {
              static_assert(std::conjunction_v<std::is_same<N, bool>...>, "Only pass bools");
          }
          std::array<bool, sizeof...(N)> arr;
          bool status;
          operator bool() { return status; }
      };
      
      int main() {
          auto check = what_failed(2 == 5, 2 < 5, 2 > 5, 1 == 1);
          if (check)
              std::cout << "Check: All true";
          else {
              std::cout << "Check: ";
              for (auto c : check.arr)
                  std::cout << c << ' ';
          }
          return 0;
      }
      

      由于构造函数中的折叠表达式和模板推导,这需要 c++17,但对于 c++11,可以通过几个额外的帮助模板来解决。

      【讨论】: