带三元回路和短路的操作员

Operator with Ternary Return and Short-Circuit

本文关键字:短路 操作员 回路 三元      更新时间:2023-10-16

我想要一个能够使truefalse的评估短路,但同时返回指示需要继续测试的运算符。


例如,两个字符串firstsecond:之间的字典字符串比较

  • 如果是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;
}

或者更一般地使用CCD_ 18。

不过,使用表达式模板,您可以完全按照要求执行,我将向您展示如何执行。

不过也有一些限制:

  1. 不能创建新的运算符,所以必须选择两个要重载的运算符:一个用于叶比较,一个用于短路组合。

    (如果你真的想的话,你可以使用一个运算符,但它会(甚至更)令人困惑,需要很多括号)

  2. 当两个操作数都是基元时,就不能真正做到这一点。如果可以的话,你的代码看起来像:

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

    但实际上,您需要将char封装在某个值容器中才能使其工作。


首先,我们需要一个简单的三态类型。我们可以列举一下:

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

对于非叶CCD_ 24:

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将向下递归到左手边的子树,这对于这个应用程序来说似乎不太合适。