Search code examples
c++scopestloperator-overloadingunordered-map

How do I privately implement an operator for an STL container?


Say we have something like this:

template<typename T>
class SparseMatrix {
    // Where the first (K,V) is (row_idx, columns) and the second is (col_idx, numeric type)  
    using Entries = std::unordered_map<unsigned long, std::unordered_map<unsigned long, T>>;
    Entries data;
    //...
};

For sparse matrices, we want to ignore rows that are mutually empty, so (I think) we could implement addition with something like this:

template<typename K, typename V>
std::unordered_map<K, V> operator+(
    const std::unordered_map<K,V>& left, 
    const std::unordered_map<K,V>& right
) {
    std::unordered_map<K, V> result(left); 
    for (auto& p: result) {
        if (right.count(p.first) > 0) {
            result[p.first] += right[p.first]; // += def'n not shown...
        }
    }

    for (auto& p: right) {
        if (result.count(p.first) == 0) { 
            result[p.first] = p.second;
        }
    }

    return result;
} 

// ...

SparseMatrix::SparseMatrix operator+(const SparseMatrix& right)
{
    // checks and whatnot...
    // Uses the custom + operator: the "sum" of two unordered_maps!!!
    return this->data + right.data;
}

I think this template is convenient because it handles the outer and inner operations simultaneously; however, because this should happen in a header file, if possible I'd like to make the unordered_map operator definition private to SparseMatrix, as it only has meaning there. I do not want std::unordered_map<K,V> operator+ to be visibly defined for anyone who #includes this implementation.

What idiomatic options do I have, other than globally declaring the operator definition(s) for the STL container?

If anything else I've drafted above is not idiomatic C++, I'd really appreciate any feedback.

Naively, I tried to paste that implementation in SparseMatrix 's private section, and ran into an error which states I'm providing too many parameters to the function. Removing the left parameter and replacing it with the private member, data, then results in the + operation not being defined for MatrixVals . My assumption for what's going on here is possibly that I've introduced a circular dependency. Is that the case?

I did not find any other SO post describing how to limit the scope of an operator overload in this way.


Solution

  • Operators for classes you don't own is generally a bad idea

    But we can still do it. Making it static or friend inside the class scope doesn't seem to work, so the best way seems to be to put it into a private namespace.

    
    namespace SparseMatrixImpl {
        
        template<typename K, typename V>
        std::unordered_map<K, V> operator+(
            std::unordered_map<K,V>& left, 
            std::unordered_map<K,V>& right
        ) {
           ...
        } 
    }
    

    and then pull that function into the overload set when you need it

    SparseMatrix::SparseMatrix operator+(const SparseMatrix& right)
    {
        // checks and whatnot...
        // Uses the custom + operator: the "sum" of two unordered_maps!!!
        using SparseMatrixImpl::operator+;
        return this->data + right.data;
    }
    

    http://coliru.stacked-crooked.com/a/5f65464c73b727e0

    The better idea

    is to simply call it something else, that is globally useful, such as accumulate.

        template<typename K, typename V>
        std::unordered_map<K, V> accumulate(
            std::unordered_map<K,V>& left, 
            std::unordered_map<K,V>& right
        ) {
           ...
        } 
    

    http://coliru.stacked-crooked.com/a/c4b22e87354ccedf


    Also you have a variety of other errors and bad ideas in the code. Namely, that you're trying to use `operator[]` on a `const` `right`.