紧凑的方式来写if(..)语句有很多相等

时间:2016-03-14 09:49:06

标签: c++ if-statement

有没有更好的方法来编写这样的代码:

if (var == "first case" or var == "second case" or var == "third case" or ...)

在Python中我可以写:

if var in ("first case", "second case", "third case", ...)

这也让我有机会轻松传递好的选项列表:

good_values = "first case", "second case", "third case"
if var in good_values

这只是一个示例:var的类型可能与字符串不同,但我只对替代(or)比较(==)感兴趣。 var可能不是const,而选项列表在编译时是已知的。

专业奖金:

  • or
  • 的懒惰
  • 编译时间循环展开
  • 易于扩展到除==
  • 之外的其他运营商

9 个答案:

答案 0 :(得分:44)

如果你想扩展编译时间,你可以使用类似这样的东西

template<class T1, class T2>
bool isin(T1&& t1, T2&& t2) {
   return t1 == t2;
}

template<class T1, class T2, class... Ts>
bool isin(T1&& t1 , T2&& t2, T2&&... ts) {
   return t1 == t2 || isin(t1, ts...);
}

std::string my_var = ...; // somewhere in the code
...
bool b = isin(my_var, "fun", "gun", "hun");

我实际上并没有对它进行测试,而且这个想法来自于亚历山大里斯的&#39; Variadic模板是funadic&#39;谈论。因此,对于细节(和正确实施),请注意。

编辑: 在c ++ 17中,他们引入了一个很好的fold expression语法

template<typename... Args>
bool all(Args... args) { return (... && args); }

bool b = all(true, true, true, false);
 // within all(), the unary left fold expands as
 //  return ((true && true) && true) && false;
 // b is false

答案 1 :(得分:27)

any_of算法在这里可以很好地运作:

#include <algorithm>
#include <initializer_list>

auto tokens = { "abc", "def", "ghi" };

bool b = std::any_of(tokens.begin(), tokens.end(),
                     [&var](const char * s) { return s == var; });

(您可能希望将tokens的范围限制在最小要求的上下文中。)

或者您创建了一个包装器模板:

#include <algorithm>
#include <initializer_list>
#include <utility>

template <typename T, typename F>
bool any_of_c(const std::initializer_list<T> & il, F && f)
{
    return std::any_of(il.begin(), il.end(), std::forward<F>(f));
}

用法:

bool b = any_of_c({"abc", "def", "ghi"},
                  [&var](const char * s) { return s == var; });

答案 2 :(得分:11)

好的,那么,你需要激进语言修改。具体来说,您想要创建自己的运算符。准备好了吗?

<强>语法

我要修改语法以使用C和C ++风格的列表:

if (x in {x0, ...}) ...

此外,我们将新的 in 运算符应用于定义了begin()end()的任何容器:

if (x in my_vector) ...

有一点需要注意:它不是一个真正的运算符,因此它必须始终用括号表示它自己的表达式:

bool ok = (x in my_array);

my_function( (x in some_sequence) );

代码

首先要注意的是,RLM通常需要一些宏观和操作员滥用。幸运的是,对于一个简单的成员谓词,滥用实际上并没有那么糟糕。

#ifndef DUTHOMHAS_IN_OPERATOR_HPP
#define DUTHOMHAS_IN_OPERATOR_HPP

#include <algorithm>
#include <initializer_list>
#include <iterator>
#include <type_traits>
#include <vector>

//----------------------------------------------------------------------------
// The 'in' operator is magically defined to operate on any container you give it
#define in , in_container() =

//----------------------------------------------------------------------------
// The reverse-argument membership predicate is defined as the lowest-precedence 
// operator available. And conveniently, it will not likely collide with anything.
template <typename T, typename Container>
typename std::enable_if <!std::is_same <Container, T> ::value, bool> ::type
operator , ( const T& x, const Container& xs )
{
  using std::begin;
  using std::end;
  return std::find( begin(xs), end(xs), x ) != end(xs);
}

template <typename T, typename Container>
typename std::enable_if <std::is_same <Container, T> ::value, bool> ::type
operator , ( const T& x, const Container& y )
{
  return x == y;
}

//----------------------------------------------------------------------------
// This thunk is used to accept any type of container without need for 
// special syntax when used.
struct in_container
{
  template <typename Container>
  const Container& operator = ( const Container& container )
  {
    return container;
  }

  template <typename T>
  std::vector <T> operator = ( std::initializer_list <T> xs )
  {
    return std::vector <T> ( xs );
  }
};

#endif

<强>用法

大!现在,我们可以在所有中使用它,希望 运算符有用。根据您的特殊兴趣,请参见示例3:

#include <iostream>
#include <set>
#include <string>
using namespace std;

void f( const string& s, const vector <string> & ss ) { cout << "nope\n\n"; }
void f( bool b ) { cout << "fooey!\n\n"; }

int main()
{
  cout << 
    "I understand three primes by digit or by name.\n"
    "Type \"q\" to \"quit\".\n\n";

  while (true)
  {
    string s;
    cout << "s? ";
    getline( cin, s );

    // Example 1: arrays 
    const char* quits[] = { "quit", "q" };
    if (s in quits) 
      break;

    // Example 2: vectors
    vector <string> digits { "2", "3", "5" };
    if (s in digits)
    {
      cout << "a prime digit\n\n";
      continue;
    }

    // Example 3: literals
    if (s in {"two", "three", "five"})
    {
      cout << "a prime name!\n\n";
      continue;
    }

    // Example 4: sets
    set <const char*> favorites{ "7", "seven" };
    if (s in favorites)
    {
      cout << "a favorite prime!\n\n";
      continue;
    }

    // Example 5: sets, part deux
    if (s in set <string> { "TWO", "THREE", "FIVE", "SEVEN" })
    {
      cout << "(ouch! don't shout!)\n\n";
      continue;
    }

    // Example 6: operator weirdness
    if (s[0] in string("014") + "689")
    {
      cout << "not prime\n\n";
      continue;
    }

    // Example 7: argument lists unaffected    
    f( s, digits );
  }
  cout << "bye\n";
}

潜在改进

为了您的特定目的,总有一些事情可以用来改进代码。您可以添加 ni (非输入)运算符(添加新的thunk容器类型)。您可以将thunk容器包装在命名空间中(这是一个好主意)。您可以专注于std::set之类的事情来使用.count()成员函数而不是O(n)搜索。等

您的其他疑虑

  • const vs mutable:不是问题;两者都可以与运营商一起使用
  • or的懒惰:从技术上讲,or 懒惰,它被短路了。 std::find()算法也以相同的方式短路。
  • 编译时循环展开:这里不太适用。你的原始代码没有使用循环;虽然std::find()可以,但任何可能发生的循环都取决于编译器。
  • 很容易扩展到==以外的运营商:这实际上是一个单独的问题;你不再看一个简单的成员谓词,但现在正在考虑一个功能性的折叠过滤器。完全有可能创建一个算法来执行该操作,但标准库提供了any_of()函数,它正是这样做的。 (它不像我们的RLM&#39;运算符那么漂亮。也就是说,任何C ++程序员都会很容易理解它。这里已经提供了这样的答案。)

希望这有帮助。

答案 3 :(得分:9)

  

首先,我建议使用for循环,这是最简单的循环   最可读的解决方案:

for (i = 0; i < n; i++) {
   if (var == eq[i]) {
      // if true
      break;
   }
}

但是,也可以使用其他一些方法,例如std::all_ofstd::any_ofstd::none_of#include <algorithm>)。

让我们看看包含所有上述关键字的简单示例程序

#include <vector>
#include <numeric>
#include <algorithm>
#include <iterator>
#include <iostream>
#include <functional>

int main()
{
    std::vector<int> v(10, 2);
    std::partial_sum(v.cbegin(), v.cend(), v.begin());
    std::cout << "Among the numbers: ";
    std::copy(v.cbegin(), v.cend(), std::ostream_iterator<int>(std::cout, " "));
    std::cout << '\\n';

    if (std::all_of(v.cbegin(), v.cend(), [](int i){ return i % 2 == 0; })) 
    {
        std::cout << "All numbers are even\\n";
    }
    if (std::none_of(v.cbegin(), v.cend(), std::bind(std::modulus<int>(),
                                  std::placeholders::_1, 2))) 
    {
        std::cout << "None of them are odd\\n";
    }
    struct DivisibleBy
    {
        const int d;
        DivisibleBy(int n) : d(n) {}
        bool operator()(int n) const { return n % d == 0; }
    };

    if (std::any_of(v.cbegin(), v.cend(), DivisibleBy(7))) 
    {
        std::cout << "At least one number is divisible by 7\\n";
    }
}

答案 4 :(得分:6)

您可以使用std :: set来测试var是否属于它。 (在启用c ++ 11的情况下编译)

#include <iostream>
#include <set>

int main()
{
    std::string el = "abc";

    if (std::set<std::string>({"abc", "def", "ghi"}).count(el))
        std::cout << "abc belongs to {\"abc\", \"def\", \"ghi\"}" << std::endl;

    return 0;
}

优点是std::set<std::string>::count适用于O(log(n))时间(其中n是要测试的字符串数量)与非紧凑if女巫相比{{1} } 一般来说。缺点是集合的构造需要O(n)。所以,构建一次,如:

O(n*log(n))

但是,IMO最好保持条件不变,除非它包含超过10个字符串来检查。使用std :: set进行此类测试的性能优势仅适用于大static std::set<std::string> the_set = {"abc", "def", "ghi"}; 。此外,对于普通的c ++开发人员来说,简单的非紧凑n更容易阅读。

答案 5 :(得分:5)

最接近的是:

template <class K, class U, class = decltype(std::declval<K>() == std::declval<U>())>
bool in(K&& key, std::initializer_list<U> vals)
{
    return std::find(vals.begin(), vals.end(), key) != vals.end();
}

我们需要使用initializer_list<U>类型的参数,以便我们可以传递像{a,b,c}这样的 braced-init-list 。这复制了元素,但可能我们正在这样做,因为我们提供文字,所以可能没什么大不了的。

我们可以这样使用:

std::string var = "hi";    
bool b = in(var, {"abc", "def", "ghi", "hi"});
std::cout << b << std::endl; // true

答案 6 :(得分:4)

如果您可以访问C ++ 14(不确定这是否适用于C ++ 11),您可以编写如下内容:

template <typename T, typename L = std::initializer_list<T>>
constexpr bool is_one_of(const T& value, const L& list)
{
    return std::any_of(std::begin(list), std::end(list), [&value](const T& element) { return element == value; });
};

电话会是这样的:

std::string test_case = ...;
if (is_one_of<std::string>(test_case, { "first case", "second case", "third case" })) {...}

或者像这样

std::string test_case = ...;
std::vector<std::string> allowedCases{ "first case", "second case", "third case" };
if (is_one_of<std::string>(test_case, allowedCases)) {...}

如果您不想将允许的案例“包装”成列表类型,您还可以编写一个小帮助函数,如下所示:

template <typename T, typename...L>
constexpr bool is_one_of(const T& value, const T& first, const L&... next) //First is used to be distinct
{
    return is_one_of(value, std::initializer_list<T>{first, next...});
};

这将允许您这样调用它:

std::string test_case = ...;
if (is_one_of<std::string>(test_case, "first case", "second case", "third case" )) {...}

Complete example on Coliru

答案 7 :(得分:2)

值得注意的是,在我看过的大多数Java和C ++代码中,列出3个左右的条件是公认的做法。它肯定比“聪明”的解决方案更具可读性。如果这种情况经常发生,那将是一个主要障碍,无论如何这都是一种设计气味,模仿或多态的方法可能有助于避免这种情况。

所以我的回答是“null”操作。只是继续做更冗长的事情,这是最被接受的。

答案 8 :(得分:0)

您可以使用开关盒。您可以拥有以下单独案例列表:

包括

使用namespace std;

int main() {     char grade =&#39; B&#39;;

switch(grade)
{
case 'A' :
case 'B' :
case 'C' :
    cout << "Well done" << endl;
    break;
case 'D' :
    cout << "You passed" << endl;
    break;
case 'F' :
    cout << "Better try again" << endl;
    break;

default :
    cout << "Invalid grade" << endl;

}

cout << "Your grade is " << grade << endl;

return 0;

}

因此,您可以将结果分组在一起:A,B和C将输出&#34;做得好&#34;。 我从Tutorials Point中拿了这个例子: http://www.tutorialspoint.com/cplusplus/cpp_switch_statement.htm