Оператор с тройным возвратом и коротким замыканием

Я хочу, чтобы оператор был способен замкнуть оценку на true или же false но также есть возврат, который указывает на необходимость продолжения тестирования.


Например, сравнение лексикографической строки между двумя строками first а также second:

  • если first[0] < second[0]мы можем закончить сравнение, вернув true
  • в противном случае, если first[0] > second[0]мы можем закончить сравнение, вернув false
  • иначе, first[0] == second[0] имеет место, и нам нужно продолжить со второго символа обеих строк.

Тривиальное решение требует двух сравнений:

bool firstIsEarlier(std::string first, std::string second){
if(first[0] < second[0]){
return true;
}else if(first[0] > second[0]){
return false;
}
// first[0] == second[0] holds
// continue with second character..
}

Мое решение взлома было использовать ifelse if блоки и int который даст мне положительное число для true или отрицательное число для false, 0 указывает на продолжение тестирования.

bool firstIsEarlier(std::string first, std::string second){
if(int i = first[0] - second[0]){
return i < 0;
}
else if(i = first[1] - second[1]){
return i < 0;
}
else if(i = first[2] - second[2]){
return i < 0;
}
return false;
}

Так что, как видите, единственный способ для меня вызвать короткое замыкание — перечислить каждое условие в else if, Хорошее решение было бы для меня способом сделать все это на одной линии и сохранить короткое замыкание. Отличным решением было бы, если бы operator# сделать что-то вроде этого:

bool firstIsEarlier(std::string first, std::string second){
return first[0] # second[0] ## first[1] # second[1] ## first[2] < second[2];
}

0

Решение

Вы, вероятно должен достичь этого с

bool firstIsEarlier(std::string first, std::string second) {
return first < second;
}

или в более общем std::lexicographical_compare,

Ты можешь сделать именно так что вы спросите с помощью шаблонов выражений, и я покажу вам, как.

Есть несколько ограничений:

  1. Вы не можете создавать новых операторов, поэтому вы должны выбрать два операторы, которые вы хотите перегрузить: один для сравнения листьев и один для комбинации коротких замыканий.

    (Вы можете использовать один оператор, если действительно хотите, но это будет (даже больше) запутанным и потребует много скобок)

  2. Вы не можете сделать это, когда оба операнда являются примитивами. Если бы вы могли, ваш код будет выглядеть так:

    bool firstIsEarlier(std::string first, std::string second){
    return first[0]^second[0] <<= first[1]^second[1] <<= first[2]^second[2];
    }
    

    но на самом деле вам нужно обернуть chars в некотором контейнере значения для его работы.


Во-первых, нам нужен простой тип с тремя состояниями. Мы можем просто перечислить это:

enum class TriState {
True = -1,
Maybe = 0,
False = 1
};

Далее нам нужно немного вещь представлять наш first[0]^second[0] выражение листа, которое оценивает наш тип с тремя состояниями:

template <typename LHS, typename RHS>
struct TriStateExpr {
LHS const &lhs_;
RHS const &rhs_;

TriStateExpr(LHS const &lhs, RHS const &rhs) : lhs_(lhs), rhs_(rhs) {}

operator bool () const { return lhs_ < rhs_; }
operator TriState () const {
return (lhs_ < rhs_ ? TriState::True :
(rhs_ < lhs_ ? TriState::False : TriState::Maybe)
);
}
};

Обратите внимание, что нам просто нужно работать operator< для наших типов — мы могли бы обобщить это, чтобы использовать явный компаратор в случае необходимости.

Теперь нам нужна неконечная часть дерева выражений. Я перенесу его в дерево выражений справа налево, поэтому левое выражение всегда является листом, а правое выражение может быть листом или полным поддеревом.

template <typename LLHS, typename LRHS, typename RHS>
struct TriStateShortCircuitExpr {
TriStateExpr<LLHS, LRHS> const &lhs_;
RHS const &rhs_;

TriStateShortCircuitExpr(TriStateExpr<LLHS, LRHS> const &lhs, RHS const &rhs)
: lhs_(lhs), rhs_(rhs)
{}

operator TriState () const {
TriState ts(lhs_);
switch (ts) {
case TriState::True:
case TriState::False:
return ts;
case TriState::Maybe:
return TriState(rhs_);
}
}

operator bool () const {
switch (TriState(lhs_)) {
case TriState::True:
return true;
case TriState::False:
return false;
case TriState::Maybe:
return bool(rhs_);
}
}
};

Теперь вам нужен синтаксический сахар, поэтому мы должны выбрать, какие операторы перегрузить. Я буду использовать ^ для листьев (на том основании, что это как < повернут на 90 градусов по часовой стрелке):

template <typename LHS, typename RHS>
TriStateExpr<LHS, RHS> operator^ (LHS const &l, RHS const &r) {
return TriStateExpr<LHS, RHS>(l,r);
}

а также <<= для не-листьев:

template <typename LLHS, typename LRHS, typename RLHS, typename RRHS>
TriStateShortCircuitExpr<LLHS, LRHS, TriStateExpr<RLHS, RRHS>>
operator<<= (TriStateExpr<LLHS, LRHS> const &l,
TriStateExpr<RLHS, RRHS> const &r) {
return TriStateShortCircuitExpr<LLHS, LRHS, TriStateExpr<RLHS, RRHS>>(l, r);
}

template <typename LLHS, typename LRHS, typename... RARGS>
TriStateShortCircuitExpr<LLHS, LRHS, TriStateShortCircuitExpr<RARGS...>>
operator<<= (TriStateExpr<LLHS, LRHS> const &l,
TriStateShortCircuitExpr<RARGS...> const &r) {
return TriStateShortCircuitExpr<LLHS, LRHS,
TriStateShortCircuitExpr<RARGS...>>(l, r);
}

Основные соображения состоят в том, что в идеале листовой оператор должен иметь более высокий приоритет, а нествольный оператор должен ассоциироваться справа налево. Если вы вместо этого использовали ассоциативный оператор слева направо, TriStateShortCircuitExpr::operator будет повторяться вниз по левому поддереву, которое кажется не элегантным для этого приложения.

2

Другие решения


По вопросам рекламы [email protected]