【问题标题】:Better way to say x == Foo::A || x == Foo::B || x == Foo::C || ...?更好的说法 x == Foo::A || x == Foo::B || x == Foo::C || ...?
【发布时间】:2012-08-27 17:31:03
【问题描述】:

假设我有一堆众所周知的值,像这样(但const char * 只是一个例子,它可能更复杂):

const char *A = "A", *B = "B", *C = "C", *D = "D", *E = "E", *F = "F", *G = "G";

现在假设如果某个表达式的结果在其中的一个子集中,我想以特定方式表现:

if (some_complicated_expression_with_ugly_return_type == A ||
    some_complicated_expression_with_ugly_return_type == C ||
    some_complicated_expression_with_ugly_return_type == E ||
    some_complicated_expression_with_ugly_return_type == G)
{
    ...
}

我发现自己经常输入这种东西,以至于我想要一个速记。

如果语言是 Python,我可以很容易地说:

if some_complicated_expression_with_ugly_return_type in [A, C, E, G]:
    ...

在 C++03 中是否有一种众所周知的、可移植的方式来类似地表达这一点?

请注意,返回类型本身是丑陋的(几乎和lambda expressions 的返回类型一样丑陋),所以我当然不想将它存储在局部变量中。

但是返回类型不一定必须与常量的类型匹配——例如,如果返回类型是std::string,它不能隐式转换到const char *,但operator == 比较适合。

到目前为止,我最好的解决方案是这样说:

const char *items[] = { A, C, E, G };
if (std::find(items, items + sizeof(items) / sizeof(*items),
              some_complicated_expression_with_ugly_return_type)
    != items + sizeof(items) / sizeof(*items))
{
    ...
}

但它非常丑陋。有没有更好的方法,也适用于非 POD?

【问题讨论】:

  • Isn't switch (some_complicated_expression_with_ugly_return_type) { case A: case B: case E: do something;break; } 够了吗?
  • 仅适用于整数类型,它们并不难看,所以这里可能不存在。
  • @Benjamin:这仅适用于整数类型,就像 Pete 所说的那样。
  • 在“最佳解决方案”中,包括返回类型。如果在那里可以接受,为什么它不能被变量接受?
  • @Benjamin:好的,我会修改示例,感谢您的反馈。

标签: c++ c++03


【解决方案1】:

如果你有 C++11:

auto res = some_complicated_expression_with_ugly_return_type;
if (res == A
    || res == C
    || res == E
    || res == G) {
}

如果没有,您仍然可以使用模板函数消除类型声明:

template <class T>
bool matches(T t) {
    return t == A || t == C || t == E || t == G;
}

if (matches(some_complicated_expression_with_ugly_return_type)) {
}

【讨论】:

  • 我特别提到我希望与 C++03 保持兼容...+1,因为它可能会帮助其他拥有 C++11 的人。
  • 你不是将res初始化为expression == A || res == C || ...吗?
  • @hvd - 是的。真可惜。我会解决的。
  • 它解决了您原始示例代码中的问题(尽管不是在我发布答案后编辑的示例)。请记住,这些帖子也是为了其他读者的利益。如果答案对您没有帮助,那并不意味着它对其他人没有帮助。
  • @hvd:呃,我一直在告诉你的是,这是一个更大的问题,而不仅仅是通用性/可重用性。这是可读性MvG 的回答使逻辑连贯——你不必每次遇到这样的if 语句时突然去寻找一个随机函数。这个相反 - 它创建意大利面条逻辑,将您的代码分支到分散在文件周围的各种函数中(祝您好运)。其中一个是可读的,另一个不是。如果你不明白我的意思,那没关系;我不知道该怎么解释,对不起。
【解决方案2】:

您可以将当前的最佳解决方案纳入模板:

template<class A, class B, size_t n>
inline bool is_in(const A &a, B (&bs)[n]) {
  return std::find(bs, bs + n, a) != bs + n;
}

你可以像这样使用

X items[] = { A, C, E, G };
if (is_in(some_complicated_expression_with_ugly_return_type, items))
  …

【讨论】:

  • 啊,我喜欢这个...但是返回类型仍然很难看。
  • @Mehrdad,哪些返回类型?你表达的那个我在这里被当作一个黑匣子,所以我在这里无能为力。您必须明确命名的唯一类型是X,即您比较的值的类型。正如您已经使用名称引用这些值一样,我假设这些值至少有一些相当短的类型名称。
  • 哦,我的错,你是对的——我把 X 与函数的返回类型混淆了。这看起来真的很不错,到目前为止我最喜欢这个!谢谢。 :)
【解决方案3】:

你可以使用switch:

switch (some_complicated_expression_with_ugly_return_type) {
  case A: case C: case E: case G:
    // do something
  default:
    // no-op
}

这仅适用于整数和枚举类型,注意。

对于更复杂的类型,可以使用C++11的auto,或者对于C++03,boost's BOOST_AUTO

auto tmp = some_complicated_expression_with_ugly_return_type;
// or
BOOST_AUTO(tmp, some_complicated_expression_with_ugly_return_type);

if (tmp == A || tmp == C || tmp == E || tmp == G) {
  // ...
}

【讨论】:

  • BOOST_AUTO 听起来非常符合 OP 的要求。
【解决方案4】:

(编辑:原来我最初使用虚拟类型的技巧不起作用,我在测试中被一次幸运的事故误导了。让我们再试一次......)

使用几个帮助模板,您可以为这种情况编写通用解决方案:

template <typename T1> class Matcher {
public:
    explicit Matcher(T1 t1): val(t1), flag(false) {}
    template <typename T2> Matcher& operator()(T2 t2)
        { flag |= val == t2; return *this; }
    operator bool() const { return flag; }
private:
    T1 val;
    bool flag;
};
template <typename T1> Matcher<T1> match(T1 t1) { return Matcher<T1>(t1); }

// example...
string s = whatever;
if (match(s)("foo")("bar")("zap")) { do_something(); }

您可以根据需要匹配任意数量的参数。

【讨论】:

  • 哦,我喜欢这个,迫不及待想试试!谢谢! +1
  • 一旦满足任何条件,原始 O​​R 表达式将完成,但 Matcher 将尝试所有重要的条件。
  • 您可以通过将 flag|=val==t2 更改为 flag=flag||val==t2 轻松解决此问题。
【解决方案5】:

类型的表达式

if (some_complicated_expression_with_ugly_return_type == A ||
    some_complicated_expression_with_ugly_return_type == C ||
    some_complicated_expression_with_ugly_return_type == E ||
    some_complicated_expression_with_ugly_return_type == G)
{
    ...
}

在代码中很常见(好吧,无论如何都是预先计算的表达式)。我认为你可以为可读性做的最好的事情是预先计算表达式并保持原样

ugly_return_type x = some_complicated_expression_with_ugly_return_type;
if (x == A ||
    x == C ||
    x == E ||
    x == G)
{
    ...
}

开发人员习惯于这种类型的语法。这使得当其他人在阅读您的代码时更容易理解

它还完美地表达了你想要的东西。这种类型的语法在现有代码中如此广泛使用是有原因的 - 因为其他替代方案的可读性更差。

当然,您可以将条件包装在一个函数中,但前提是它是可重用的并且在逻辑上有意义(除了 IMO 一点)。

【讨论】:

  • 获取返回类型并不总是那么容易,尤其是如果您使用的是 bind/lambda 和其他东西。
【解决方案6】:

这可以使用 c++03 中的可变参数函数来完成,如下所示:

template <typename T>
bool MatchesOne( T _lhs, int _count, ...)
{
    va_list vl;
    va_start(vl,_count);
    for (int i=0;i<_count;i++)
    {
        int rhs=va_arg(vl,int);
        cout << "rhs = " << rhs << endl;
        if (_lhs == rhs) return true;
    }
    va_end(vl);
    return false;
}

int main(){
    float ff = 3.0;
    if (MatchesOne(ff, 5, 1, 2, 4, 5, 3))
    {
        cout << "Matches" << endl;
    }
    return 0;
}

如果你知道所有表达式的类型都和_lhs一样,你可以把int rhs=va_arg(vl,int);改成T rhs=va_arg(vl,T);

您也可以使用 c++11 中的可变参数模板优雅地做到这一点:

template<typename T, typename T2>
bool one_equal(const T & _v1, const T2 & _v2)
{
    return _v1 == _v2;
}

template<typename T, typename T2, typename... Args>
bool one_equal(const T & _v1, const T2 & _v2, Args... args)
{
    return _v1 == _v2 || one_equal(_v1, args...);
}

...

if (one_equal(some_complicated_expression, v1, v2, v3, v4))
{

}

好的,最后一个 hack-ish 解决方案。可以,但是让这个函数的实现者做了很多重复的工作。

template <typename T1, typename T2>
bool match_one(T1 _v1, T2 _v2)
{
    return _v1 == _v2;
}

template <typename T1, typename T2, typename T3>
bool match_one(T1 _v1, T2 _v2, T3 _v3)
{
    return _v1 == _v3 || match_one(_v1, _v2);
}

template <typename T1, typename T2, typename T3, typename T4>
bool match_one(T1 _v1, T2 _v2, T3 _v3, T4 _v4)
{
    return _v1 == _v4 || match_one(_v1, _v2, _v3);
}

template <typename T1, typename T2, typename T3, typename T4, typename T5>
bool match_one(T1 _v1, T2 _v2, T3 _v3, T4 _v4, T5 _v5)
{
    return _v1 == _v5 || match_one(_v1, _v2, _v3, _v4);
}

【讨论】:

  • 我也想知道。可能他从未见过可变参数模板。
  • C 夫妇笔记...关于 C++11 版本:它完全忽略了我在问题中说 C++03 的事实。关于 C++03 版本:可变参数对于非 POD 非常危险,并且代码容易出错,因为 count 参数很容易与其余参数不同步。
  • 又添加了一个尝试 - c++03,没有 va_args。需要您进行一些忙碌的工作,但是如果您在整个程序中经常使用它并且比较次数相当少,那么实施它不会超过几分钟。与其他解决方案相比的优势在于它允许比较 any 类型,因此右侧的所有变量不必属于同一类型(原始问题未规定一种或另一种方式)。
【解决方案7】:

如果不切换,可能是这样的,我没有使用它,但可能是工作的草稿?

template <class ReturnType>
bool average(ReturnType expression, int count, ...)
{
  va_list ap;
  va_start(ap, count); //Requires the last fixed parameter (to get the address)
  for(int j=0; j<count; j++)
    if(expression==va_arg(ap, ReturnType))
      return true;
    return false
  va_end(ap);
}

【讨论】:

  • 可变参数对于非 POD 非常危险。
【解决方案8】:

C++11:

template<typename T1, typename T2>
bool equalsOneOf (T1&& value, T2&& candidate) {
   return std::forward<T1>(value) == std::forward<T2>(candidate);
}

template<typename T1, typename T2, typename ...T>
bool equalsOneOf (T1&& value, T2&& firstCandidate, T&&...otherCandidates) {
   return (std::forward<T1>(value) == std::forward<T2>(firstCandidate))
     || equalsOneOf (std::forward<T1> (value), std::forward<T>(otherCandidates)...);
}

if (equalsOneOf (complexExpression, A, D, E)) { ... }

C++03:

template<typename T, typename C>
bool equalsOneOf (const T& value, const C& c) { return value == c; }

template<typename T, typename C1, typename C2>
bool equalsOneOf (const T& value, const C1& c1, const C2& c2) {
    return (value == c2) || equalsOneOf (value, c1);
}

template<typename T, typename C1, typename C2, typename C3>
bool equalsOneOf (const T& value, const C1& c1, const C2& c2, const C3& c3) {
    return (value == c3) || equalsOneOf (value, c1, c2);
}

template<typename T, typename C1, typename C2, typename C3, typename C4>
bool equalsOneOf (const T& value, const C1& c1, const C2& c2, const C3& c3, const C4& c4) {
    return (value == c4) || equalsOneOf (value, c1, c2, c3);
}

template<typename T, typename C1, typename C2, typename C3, typename C4, typename C5>
bool equalsOneOf (const T& value, const C1& c1, const C2& c2, const C3& c3, const C4& c4, const C5& c5) {
    return (value == c5) || equalsOneOf (value, c1, c2, c3, c4);
}

// and so on, as many as you need

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-11-11
    • 2011-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-26
    • 2013-07-08
    • 1970-01-01
    相关资源
    最近更新 更多