Is it possible to overload operator associativity

2019-05-11 13:51发布

问题:

I'm building a class that has a slightly asymmetric addition. Before the complaints come in, it's necessarily asymmetric. There is a conversion (operation that takes a bit of time) that has to happen when two objects are added together, and the conversion most naturally happens with respect to the right summand.

To make this concrete, here's a generic example of what's going on...

class Foo {
    char _fav;
    int _prop;

public:
    const char fav() const {return _fav;}
    const int prop() const (return _prop;}
    void changeFav(char); // complicated method that also changes _prop
    void changeProp(int); // straightforward method
}

Foo
operator + (Foo A, Foo B) {
    Foo sum;
    if (A.fav() != B.fav()) A.changeFav(B.fav);
    sum.changeProp(A.prop() + B.prop());   
    return sum;
}

In order for two Foos to be added, they need to have the same _fav, so a choice must be made as to which to convert. Based on the details of Foo, it's most natural to change the left summand to match the right summand.

However, when doing:

Foo A,B,C;
Foo D = A + B + C; // D = (A + B) + C
Foo E = A + (B + C);

if A already has the same _fav as C, then changeFav is called twice for D (once to change A._fav to B._fav and then again to change (A+B)._fav to C._fav) and once for E (to change B._fav to C._fav). I prefer the latter but want to avoid forcing the user to use parentheses for multiple additions.

Is there a way to overload the associativity of operator + to make this happen?

回答1:

From c++ standard clause 5,

Overloaded operators obey the rules for syntax specified in Clause 5.

Where clause 5 specifies operator precedence and associativity.



回答2:

You can do a hack. You have to use another type to hold the intermediate results of the operation, then you can use an implicit cast to evaluate the result. Here is an example of how to implement Python-style comparison operators in C++:

#include <vector>
#include <cstdio>

struct S {
    S(int x) : val(x) { }
    int val;
};

struct Comparison {
    std::vector<S> operands;
    explicit Comparison(S x)
    {
        operands.push_back(x);
    }
    operator S()
    {
        auto i = operands.begin(), e = operands.end();
        S prev = *i;
        for (i++; i != e; i++) {
            S cur = *i;
            if (prev.val >= cur.val)
                return S(0);
            prev = cur;
        }
        return S(1);
    }
    void append(const Comparison &a)
    {
        operands.insert(
            operands.end(),
            a.operands.begin(),
            a.operands.end());
    }
    void append(const S &a)
    {
        operands.push_back(a);
    }
};

Comparison operator<(const Comparison &left, const Comparison &right)
{ Comparison result(left); result.append(right); return result; }
Comparison operator<(const Comparison &left, const S &right)
{ Comparison result(left); result.append(right); return result; }
Comparison operator<(const S &left, const Comparison &right)
{ Comparison result(left); result.append(right); return result; }
Comparison operator<(const S &left, const S &right)
{ Comparison result(left); result.append(right); return result; }

int main()
{
    S x(0);
    x = S(0) < S(1) < S(2) < S(3);
    std::printf("0 < 1 < 2 < 3 = %d\n", x.val);
    x = S(0) < S(1) < S(3) < S(2);
    std::printf("0 < 1 < 3 < 2 = %d\n", x.val);
    return 0;
}

In such a situation, however, I would be quick to ditch the + operator. I would avoid using + for any operation that is not associative and commutative, since that is the convention for + in mathematics. Instead, you can use a variadic function (using templates) to perform the desired computation.



回答3:

Kinda, but you will not like the "how" of it.

First off, you need to go read and understand the Boost.Proto documentation. Then, you need to figure out how to transform all of your expression trees to reverse the order of operations. Then you need to make evaluation of the expression trees transparent to the end user. Possible on assignment? I've not messed around much with Proto, but something similar to this article on Proto-based optimizations might be a helpful place to start.



回答4:

No. So why not change the favness of the right-hand operand instead?