Commutative operator overloading + of 2 different objects
Asked Answered
S

3

10

I have 2 classes which represent a matrix:
1. RegularMatrix - O(n^2) representation
2. SparseMatrix - a matrix that is represented as linked list (without zeros).

lets say i have:

RegularMatrix a;
SparseMatrix b;

i want to be able to do:

a+b;

and also:

b+a;

so i'm overloading the + operator. My question is, since I want the addition to be commutative (a+b = b+a), do i need to implement 2 overloadings, one for each case?

RegularMatrix operator+(const RegualarMatrix &, const SparseMatrix &);
RegularMatrix operator+(const SparseMatrix & ,const RegualarMatrix &);

or is there a general form which the compiler decides by itself?

Thank you

Suggs answered 21/9, 2010 at 21:22 Comment(0)
I
14

Yes you need both versions. But you can forward the one to the other, if the operation really is commutative

RegularMatrix operator+(const SparseMatrix &a, const RegualarMatrix &b) {
    return b + a;
}
Insider answered 21/9, 2010 at 21:27 Comment(4)
Thank you.. And one more thing i'm curious about, is it considered better programming to return a reference of RegularMatrix than to return the object by value?Suggs
@dmckee: To be fair, it was more than a minute. :)Unlikelihood
@sbi: Granted. But I got the notification just as I as reaching for my mouse to post mine...Gomphosis
@dmckee: I hear you. The same just happened to me right after I wrote that comment to you...Unlikelihood
O
2

Both versions are required, just write after first overload:

RegularMatrix operator+(const SparseMatrix &a, const RegualarMatrix &b)
{
    return operator+(b,a);
}

or simpler version:

RegularMatrix operator+(const SparseMatrix &a, const RegualarMatrix &b)
{
    return b + a;
}
Observable answered 3/1, 2016 at 19:24 Comment(0)
T
2

Unless you have a huge amount of operators with complicated signatures that all need to be duplicated for commutative behaviour, I would just use the solution from the accepted answer. However, if you really hate repeating your code or want to make it work just for the sake of it:

#include <iostream>    // std::cout
#include <utility>     // std::pair
#include <type_traits> // std::remove_cvref_t
#include <concepts>    // std::same_as

// These two utilities will be used for all commutative functions:

template <typename T, typename U, typename V, typename W>
concept commutative =
    (std::same_as<std::remove_cvref_t<T>, V> && std::same_as<std::remove_cvref_t<U>, W>) ||
    (std::same_as<std::remove_cvref_t<U>, V> && std::same_as<std::remove_cvref_t<T>, W>);

template <typename V, typename W, typename T, typename U>
requires commutative<T, U, V, W>
constexpr decltype(auto) order (T && a, U && b) {
    if constexpr (std::same_as<std::remove_cvref_t<T>, V>)
        return std::pair{std::forward<T>(a), std::forward<U>(b)};
    else
        return std::pair{std::forward<U>(b), std::forward<T>(a)};
}

// Here goes the use-case:

struct A {
    int aval;
};

struct B {
    int bval;
};

// This template declaration allows two instantiations:
// (A const &, B const &) and (B const &, A const &)
template <typename T, commutative<T, A, B> U>
A operator + (T const & first, U const & second) {
    // But now we need to find out which is which:
    auto const & [a, b] = order<A, B>(first, second);
    return {.aval = a.aval + b.bval};
}

// Just to test it:
int main () {
    A a = {.aval = 1};
    B b = {.bval = 2};

    A c = a + b;
    A d = b + a;

    std::cout << c.aval << '\n';
    std::cout << d.aval << '\n';
}

If template <typename T, commutative<T, A, B> U> and auto const & [a, b] = order<A, B>(first, second); is less boilerplate code than repeating the whole definition with return b + a; for your scenario, then I guess it could be useful.

Tannic answered 9/7, 2021 at 14:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.