使用比较运算符时检查多个值

时间:2012-07-20 16:41:22

标签: c++ operator-overloading logical-operators expression-templates

我一直认为,对于任何比较语句,即X == YX != Y是格式,您将语句与&&||链接在一起。

有没有办法写X == (Y || Z)代替X == Y || X == Z

编辑:既然已经确定这不可能干净利落,那还有什么可以做呢?

4 个答案:

答案 0 :(得分:8)

#include <algorithm>
#include <array>
#include <string>
#include <iostream>
#include <initializer_list>

template<class Type, class Next>
bool is_one_of(const Type& needle, const Next& next)
{return needle==next;}
template<class Type, class Next, class ... Rest>
bool is_one_of(const Type& needle, const Next& next, Rest... haystack)
{return needle==next || is_one_of(needle, haystack...);}

int main() {
    std::string X, Y;
    if (is_one_of(X, Y, "HI"))
        std::cout << "it is!";
    else
        std::cout << "it isn't!";
    return 0;
}

proof of compilationXeo还注意到std::any_ofstd::all_ofstd::none_of可能有用,具体取决于您的实际需求和愿望。

答案 1 :(得分:2)

在C ++中没有干净的方法来做你所要求的。

许多人的出行是X == (Y || Z)可能是一个法律表达,编译器不会抱怨。这只是一个bug。 每个C ++语句必须自己评估为true / false,并且运算符只是将它们串在一起。你的建议需要一些内在的列表结构。许多语言(如Python),但C ++没有。

答案 2 :(得分:1)

使用运算符重载,您可能能够获得所需的确切语法。但是,正如亚当指出的那样,这可能导致排除有效的表达。

下面是一个带有运算符重载,模板函数和宏的模板,以实现类似于Mooing Duck更好的解决方案的语法,但不需要C ++ 11,并允许使用||运算符表示“干草堆”系列。

template <typename T>
struct MultiOrComparable {
    mutable std::set<T> vals;
    const MultiOrComparable & operator || (T v) const {
        vals.insert(v); return *this;
    }
    bool operator == (T v) const { return vals.find(v) != vals.end(); }
};

template <typename T>
MultiOrComparable<T> MultiOrComparableStart (T) {
    return MultiOrComparable<T>();
}

#define IsOneOf(x, y) ((MultiOrComparableStart(x)||y) == x)

然后,以下程序“有效”:

enum Foo { A, B, C, D };

int
main ()
{
    if (!IsOneOf(A, B || C || D)) {
        std::cout << "!=" << std::endl;
    }
    if (IsOneOf('a', 'x' || 'y' || 'z' || 'a')) {
        std::cout << "==" << std::endl;
    }
}

答案 3 :(得分:1)

可能有办法通过expression templates实现您想要的效果。下面是一个草图如何处理这个(不编译,丢失了很多细节,告诫者)。首先,您需要设置一个类模板来表示逻辑值,并在其上定义一些运算符。

template<typename T, typename Type = Atomic<T> >
class Logical;

template<typename T, typename E1, typename E2>
Logical<T, OpOr<T, E1, E2> > operator||(Logical<T, E1> lhs, Logical<T, E2> rhs); 

template<typename T, typename E1, typename E2>
Logical<T, OpAnd<T, E1, E2> > operator&&(Logical<T, E1> lhs, Logical<T, E2> rhs); 

template<typename T, typename E1, typename E2>
Logical<T, OpEq<T, E1, E2> > operator==(Logical<T, E1> lhs, Logical<T, E2> rhs)
{ return OpEq<T, E1, E2>()(lhs, rhs); } // delegate to class template

由于功能模板不能部分专业化,因此您将实际工作委托给类模板。

// primary template
template<typename T, typename E1, typename E2> class OpEq; 

// specialization for atomic comparisons
template<typename T>
class OpEq<T, Atomic<T>, Atomic<T> >
{
    bool operator()(Atomic<T> lhs, Atomic<T> rhs)
    { return lhs == rhs; }
}

// apply distributive rule
template<typename T>
class OpEq<T, Atomic<T>, OpOr<T, Atomic<T>, Atomic<T> > >
{
    bool operator()(Atomic<T> lhs, OpOr<T, Atomic<T>, Atomic<T> > rhs)
    { return (lhs == rhs.first()) && (lhs == rhs.second()); }
}

显然,有很多繁重的模板机制可以为你想要的东西获得自然的C ++语法。但是经过大量的努力和阅读,你最终可能会得到一些不错的东西。 (您必须定义Atomic,OpAnd,OpOr,设置子表达式的第一和第二分支的设置表示等。)

但是,即使你成功了,你的方案中也会出现奇怪的语义。您提议的是要求== 左分配而不是||&&。即解析

X == (Y @OP Z) 

as

(X == Y) @OP (X == Z)

@OP等于&&||。我认为要求==保持对称是很自然的。这还需要您将==的{​​strong>右分布强加于&&||。即解析

(X @OP Y) == Z 

作为

(X == Z) @OP (Y == Z)

但是,如果将两者与表达式(A @OP1 B) == (C @OP2 D)合并,则会出现逻辑上的不一致。例如。结果取决于您应用左分布和右分布的顺序。

<强>左 - 则 - 右

(A @OP1 B) == (C @OP2 D)
((A @OP1 B) == C) @OP2 ((A @OP1 B) == D)
((A == C) @OP1 (B ==C)) @OP2 ((A == D) @OP1 (B == D))

从右-然后左

(A @OP1 B) == (C @OP2 D)
(A == (C @OP2 D)) @OP1 (B == (C @OP2 D))
((A == C) @OP2 (A == D)) @OP1 ((B == C) @OP2 (B == D))

在这两种情况下,正在比较相同的4对元素,但它们在表达式树上向上传播的方式略有不同。如果@OP1@OP2相同,那么您可以展平整个树并重新排序这些术语以获得唯一结果。如果你在==的两边都使用相同的运算符,那么就可以了,因为&&||都是关联以及交换即可。

但对于混合运算符,结果表达式通常会有所不同。

更新:如对此答案和其他答案的评论中所述,您还会忽略内置类型的某些属性。首先是短路规则,超载运营商不遵守这些规则。对于不涉及指针解除引用或其他资源访问(if(p && p->value())if(file && file.open())等)的逻辑表达式,这不会影响正确性,只影响效率。否则小心!其次,还提到对常量/表达式的混合评估会出错。这有一个简单(但冗长)的修复:只需使用std::integral_constant(或boost::mpl::int_)作为包装。