4

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?

PengOne
  • 48,188
  • 17
  • 130
  • 149
  • Your `operator+` doesn't return anything, and two member functions are missing return types. – Lightness Races in Orbit Jan 30 '14 at 00:39
  • 1
    Can't provide complete answer but in short, don't return a foo and make the right operand something other than foo. This is just stream of consciousness typed from my o hone. – John Dibling Jan 30 '14 at 00:40
  • @LightnessRacesinOrbit Yes, because this is just a dummy example so you can see what's going on. It's not real code. – PengOne Jan 30 '14 at 00:42
  • 1
    @PengOne: We generally expect your code to at least be valid C++. :) – Lightness Races in Orbit Jan 30 '14 at 00:45
  • @LightnessRacesinOrbit Not when the class is called `Foo` and the intent is clear. – PengOne Jan 30 '14 at 00:46
  • 7
    @PengOne: Yes, always. When you write pseudo-code with mistakes in it, we have no guarantee that your question is the question you meant to ask. It's easy to ensure that you remember to write return types and return statements, so yes we ask you to do that. – Lightness Races in Orbit Jan 30 '14 at 00:47
  • 1
    Isn't changing the associativity just going to fix this one example? You can come up with a scenario where left-associative would yield faster code than right-associative and vice versa. Why not should just a variadic `Foo sum(Foos...)` function and implement it there? – pmr Jan 30 '14 at 00:52
  • @pmr No. If the associativity is reversed, the there is never a toggle as in the example. Everything will be changed to the rightmost object's `_fav`. In particular, those that match will not be changed. – PengOne Jan 30 '14 at 00:55
  • Downvoted. Operator overloading should always preserve the same semantics as for builtins ("do as the ints do"). This is why people are warned against overloading logical and comma operators. Just use a named function `left_assoc_add`. – TemplateRex Jan 30 '14 at 08:24

4 Answers4

3

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.

Dietrich Epp
  • 205,541
  • 37
  • 345
  • 415
  • I like the spirit of this. About `+`, when adding two linear transformations in math, they must first be expressed in the same basis, which is very similar to my example. – PengOne Jan 30 '14 at 01:05
  • @PengOne: It is not necessary to express linear transformations using a basis to add them—not all linear transformations can be expressed using a basis, anyway. You can just define "(f + g)(x) = f(x) + g(x)". – Dietrich Epp Jan 30 '14 at 01:07
  • True, but I'm illustrating that my example is natural if `_fav` is the chosen basis and `_prop` is the matrix representation wrt that basis. – PengOne Jan 30 '14 at 01:11
2

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

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • It's complicated and gets into the technical details of (the real) `Foo` and what `+` means for `Foo`. Can you elaborate on "No"? Is this written in stone or you just have never seen it done? – PengOne Jan 30 '14 at 00:43
  • 2
    @PengOne: If your question is "can I change the associativity of `+`" then the answer is "no you can't". It's not just that I "have never seen it done": it's a fact of the language grammar. – Lightness Races in Orbit Jan 30 '14 at 00:46
  • 2
    @LightnessRacesinOrbit: It is, however, possible to make `A+B` act as if it had the opposite associativity, which is pretty much the same result, if far more code. – Mooing Duck Jan 30 '14 at 01:19
2

From c++ standard clause 5,

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

Where clause 5 specifies operator precedence and associativity.

user534498
  • 3,926
  • 5
  • 27
  • 52
2

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.

KitsuneYMG
  • 12,753
  • 4
  • 37
  • 58