Проверка нескольких значений при использовании операторов сравнения

У меня всегда было впечатление, что для любого оператора сравнения, то есть X == Y или X != Y, это формат, и вы связываете операторы вместе с && или ||.

Нет ли способа написать X == (Y || Z) вместо X == Y || X == Z?

Редактировать: Поскольку было установлено, что это невозможно сделать чисто, как еще это можно было сделать?


person Drise    schedule 20.07.2012    source источник
comment
Вероятно, у вас может быть функция с переменным числом аргументов, принимающая первый аргумент как X, а остальные как Y, Z и т. д. Отменяет синтаксис оператора, но все еще работает: if (areAllEqual (X, Y, Z, A, B, C))   -  person chris    schedule 20.07.2012
comment
Хороший вопрос, меня то же самое интересовало..   -  person Nils    schedule 20.07.2012


Ответы (4)


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

доказательство компиляции. Xeo также отмечает, что std::any_of, std::all_of и std::none_of могут быть полезны в зависимости от ваших реальных потребностей и желаний.

person Mooing Duck    schedule 20.07.2012
comment
std::initializer_list<E> должно помочь. ideone.com/UvwYV Обратите внимание, что при этом копируются операнды. - person Xeo; 20.07.2012
comment
Однако это имеет некоторые накладные расходы на производительность - некоторые выделения, создание временного массива, зацикливание на массиве. - person orlp; 20.07.2012
comment
@Xeo: На самом деле у меня было 99% кода, но он терпел неудачу, поэтому я был на волоске от того, чтобы сдаться, когда увидел ваш комментарий. Когда я снова посмотрел на свой код, я увидел, что в нем все еще есть template<class T, unsigned L>, вызывающий сбой. Спасибо за комментарий! - person Mooing Duck; 20.07.2012
comment
@nightcracker: Да, он копирует каждый из X и Y в список инициализаторов, а затем уничтожает их в конце. - person Mooing Duck; 20.07.2012
comment
Кроме того, это не поддерживает смешивание типов. - person orlp; 20.07.2012
comment
@nightcracker: немного подумав, я решил, что сравнение std::string с литералами — полезная идея, и добавил смешивание типов. Это просто означает, что функция может не быть найдена, если вы смешиваете типы в списке инициализатора, вместо того, чтобы просто продвигать типы. - person Mooing Duck; 20.07.2012
comment
@nightcracker: я полностью и полностью переписал. Больше никаких итераций, копий и разрешений смешанных типов. - person Mooing Duck; 20.07.2012

Нет чистого способа сделать то, что вы просите, на С++.

Многих сбивает с толку то, что X == (Y || Z) может быть допустимым выражением, и компилятор не будет жаловаться. Это будет просто ошибка. Каждый оператор C++ должен оцениваться как истина/ложь сам по себе, а операторы просто связывают их вместе. То, что вы предлагаете, потребует некоторой внутренней структуры списка. Это есть во многих языках (например, в Python), но не в C++.

person Adam    schedule 20.07.2012
comment
X == (Y || Z): Насколько я понимаю, я мог видеть if (x == (4 || 5)), которое затем оценивается как if (x == true). Я определенно вижу, как это сбивает с толку новых программистов. - person Drise; 20.07.2012

С перегрузкой операторов вы можете получить именно тот синтаксис, который вам нужен. Но, как указывает Адам, это может привести к исключению правильных выражений.

Ниже приведен шаблон с перегрузкой оператора, функцией шаблона и макросом для достижения синтаксиса, похожего на более приятное решение 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;
    }
}
person jxh    schedule 20.07.2012

Может быть способ добиться того, чего вы хотите, с помощью шаблонов выражений. Ниже приведен набросок того, как к этому подойти (не компилируется, отсутствует много деталей, предупредите лектора). Сначала вы настраиваете шаблон класса для представления логических значений и определяете над ними некоторые операторы.

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 равно && или ||. Я думаю, было бы естественно потребовать, чтобы == оставался симметричным. Это потребует от вас также наложить право-распределение == на && и ||. т.е. разбирать

(X @OP Y) == Z 

as

(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_) в качестве оболочки.

person TemplateRex    schedule 20.07.2012
comment
Одна очень большая проблема с перегрузкой логических операторов заключается в том, что вы теряете очень важное свойство короткого замыкания, о котором вы вообще не упоминаете. - person Xeo; 21.07.2012
comment
@Xeo Я не упомянул об этом, потому что это было а) настолько очевидным и б) потому что я думал, что ОП готов заплатить эту цену. Тем не менее, в т.ч. библиотека шаблонов выражений для вычислений символьной логики, шансы сделать ошибки указателей были бы очень малы, не так ли? - person TemplateRex; 21.07.2012
comment
@Xeo В любом случае, я обновил ответ вашим комментарием. Спасибо еще раз. - person TemplateRex; 21.07.2012