Is there actually a reason why overloaded && and || don't short circuit?

前端 未结 9 1748
礼貌的吻别
礼貌的吻别 2020-11-28 21:54

The short circuiting behaviour of the operators && and || is an amazing tool for programmers.

But why do they lose this behaviour w

9条回答
  •  陌清茗
    陌清茗 (楼主)
    2020-11-28 22:25

    With retrospective rationalization, mainly because

    • in order to have guaranteed short-circuiting (without introducing new syntax) the operators would have to be restricted to results actual first argument convertible to bool, and

    • short circuiting can be easily expressed in other ways, when needed.


    For example, if a class T has associated && and || operators, then the expression

    auto x = a && b || c;
    

    where a, b and c are expressions of type T, can be expressed with short circuiting as

    auto&& and_arg = a;
    auto&& and_result = (and_arg? and_arg && b : and_arg);
    auto x = (and_result? and_result : and_result || c);
    

    or perhaps more clearly as

    auto x = [&]() -> T_op_result
    {
        auto&& and_arg = a;
        auto&& and_result = (and_arg? and_arg && b : and_arg);
        if( and_result ) { return and_result; } else { return and_result || b; }
    }();
    

    The apparent redundancy preserves any side-effects from the operator invocations.


    While the lambda rewrite is more verbose, its better encapsulation allows one to define such operators.

    I’m not entirely sure of the standard-conformance of all of the following (still a bit of influensa), but it compiles cleanly with Visual C++ 12.0 (2013) and MinGW g++ 4.8.2:

    #include 
    using namespace std;
    
    void say( char const* s ) { cout << s; }
    
    struct S
    {
        using Op_result = S;
    
        bool value;
        auto is_true() const -> bool { say( "!! " ); return value; }
    
        friend
        auto operator&&( S const a, S const b )
            -> S
        { say( "&& " ); return a.value? b : a; }
    
        friend
        auto operator||( S const a, S const b )
            -> S
        { say( "|| " ); return a.value? a : b; }
    
        friend
        auto operator<<( ostream& stream, S const o )
            -> ostream&
        { return stream << o.value; }
    
    };
    
    template< class T >
    auto is_true( T const& x ) -> bool { return !!x; }
    
    template<>
    auto is_true( S const& x ) -> bool { return x.is_true(); }
    
    #define SHORTED_AND( a, b ) \
    [&]() \
    { \
        auto&& and_arg = (a); \
        return (is_true( and_arg )? and_arg && (b) : and_arg); \
    }()
    
    #define SHORTED_OR( a, b ) \
    [&]() \
    { \
        auto&& or_arg = (a); \
        return (is_true( or_arg )? or_arg : or_arg || (b)); \
    }()
    
    auto main()
        -> int
    {
        cout << boolalpha;
        for( int a = 0; a <= 1; ++a )
        {
            for( int b = 0; b <= 1; ++b )
            {
                for( int c = 0; c <= 1; ++c )
                {
                    S oa{!!a}, ob{!!b}, oc{!!c};
                    cout << a << b << c << " -> ";
                    auto x = SHORTED_OR( SHORTED_AND( oa, ob ), oc );
                    cout << x << endl;
                }
            }
        }
    }
    

    Output:

    000 -> !! !! || false
    001 -> !! !! || true
    010 -> !! !! || false
    011 -> !! !! || true
    100 -> !! && !! || false
    101 -> !! && !! || true
    110 -> !! && !! true
    111 -> !! && !! true
    

    Here each !! bang-bang shows a conversion to bool, i.e. an argument value check.

    Since a compiler can easily do the same, and additionally optimize it, this is a demonstrated possible implementation and any claim of impossibility must be put in the same category as impossibility claims in general, namely, generally bollocks.

提交回复
热议问题