Я хочу, чтобы оператор был способен замкнуть оценку на 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..
}
Мое решение взлома было использовать if
—else 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];
}
Вы, вероятно должен достичь этого с
bool firstIsEarlier(std::string first, std::string second) {
return first < second;
}
или в более общем std::lexicographical_compare
,
Ты можешь сделать именно так что вы спросите с помощью шаблонов выражений, и я покажу вам, как.
Есть несколько ограничений:
Вы не можете создавать новых операторов, поэтому вы должны выбрать два операторы, которые вы хотите перегрузить: один для сравнения листьев и один для комбинации коротких замыканий.
(Вы можете использовать один оператор, если действительно хотите, но это будет (даже больше) запутанным и потребует много скобок)
Вы не можете сделать это, когда оба операнда являются примитивами. Если бы вы могли, ваш код будет выглядеть так:
bool firstIsEarlier(std::string first, std::string second){
return first[0]^second[0] <<= first[1]^second[1] <<= first[2]^second[2];
}
но на самом деле вам нужно обернуть char
s в некотором контейнере значения для его работы.
Во-первых, нам нужен простой тип с тремя состояниями. Мы можем просто перечислить это:
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
будет повторяться вниз по левому поддереву, которое кажется не элегантным для этого приложения.