Search code examples
c++c++11operator-overloading

Clarification regarding operator overloading


To provide context I have an executable that depends on another Foo library that provides an implementation for the Identifier class, now module class in my executable needs to cache the Foo object keyed on Identifier, so I added logic as shown below

class Module {
public:
    
    static std::shared_ptr<Module> create();

    explicit Module() = default;

    ...
protected:
    ...
    /// Map that caches Foo object keyed on @c Identifier.
    std::map<Identifier,Foo> m_FooMap;
};

in my Module.CPP I am inserting into the map as follows
Foo foo; m_FooMap.emplace(identifier, foo); But when I try to compile I get the below error

/local/home/workspace/build/usr/include/c++/v1/__functional/operations.h:372:21: error: invalid operands to binary expression ('const Identifier' and 'const Identifier')
        {return __x < __y;}
                ~~~ ^ ~~~
/local/home/workspace/build/usr/include/c++/v1/map:598:17: note: in an instantiation of member function 'std::less<Identifier>::operator()' requested here
        {return static_cast<const _Compare&>(*this)(__x, __y.__get_value().first);}
                ^
/local/home/workspace/build/usr/include/c++/v1/__tree:1974:17: note: in instantiation of member function 'std::__map_value_compare<Identifier, Foo>, std::less<Identifier>, true>::operator()' requested here
            if (value_comp()(__v, __nd->__value_))
                ^

As per my understanding, it looks like the Identifier class is missing the less than operator and since I cannot make changes to the Foo library that provide an implementation of Identifier, I tried to overload both less than and equal to an operator for Identifier in the Module.h file itself

// Overloading the less-than operator for the Identifier
// objects
bool operator<(const Identifier& lhs, const Identifier& rhs){
    return (lhs.field1 < rhs.field1);
}

// Overloading the equal to the operator for the Identifier
// objects
bool operator==(const Identifier& lhs, const Identifier& rhs){
    return (lhs.field1 == rhs.field1);
}

But I still see the same error. So wanted to clarify whether my understanding is correct that the Identifier class is missing less than operator overloading and once that is provided we can key based on Identifier. Also since I cannot modify logic in the Foo library that provides an Identifier class can I provide my own custom operator overloading of the Identifier class as shown above or is that not feasible and if so what are the alternatives?


Solution

  • At least as I read things, your current situation is pretty much like this:

    #include <string>
    #include <map>
    
    // defined in some other file, but class definition included via a header:
    namespace library { 
        class Identifier {
        public:
            Identifier(std::string const &s) : data(s) {}
    
            explicit operator std::string() const { return data; }
            std::string data;
        };
    }
    
    bool operator<(library::Identifier const &a, library::Identifier const &b) { 
        return a.data < b.data;
    }
    
    class Module {
        std::map<library::Identifier, int> data;
    public:
        void insert(library::Identifier const &s, int i) { 
            data[s] = i;
        }
    };
    
    int main() { 
    
        Module m;
        library::Identifier i("foo");
    
        m.insert(i, 1);
    }
    

    You have Identifier defined by some library. In your own class, you have a map that tries to use an Identifier as the key. Since Identifier itself doesn't define operator<, you've defined one yourself. But it's still not found, and compilation still fails, just like if you hadn't defined operator< at all.

    I've defined Identifier inside a namespace, because I'm reasonably certain that's the case in your code. And more importantly, it's being in a namespace is the real reason the compiler doesn't find and won't use the operator you defined.

    Fortunately, it's fairly easy to fix that. You need to put your operator in the same namespace as Identifier itself is defined:

    #include <string>
    #include <map>
    
    // defined in some other file, but class definition included via a header:
    namespace library { 
        class Identifier {
        public:
            Identifier(std::string const &s) : data(s) {}
    
            explicit operator std::string() const { return data; }
            std::string data;
        };
    }
    
    namespace library {
    bool operator<(library::Identifier const &a, library::Identifier const &b) { 
        return a.data < b.data;
    }
    }
    
    class Module {
        std::map<library::Identifier, int> data;
    public:
        void insert(library::Identifier const &s, int i) { 
            data[s] = i;
        }
    };
    
    int main() { 
    
        Module m;
        library::Identifier i("foo");
    
        m.insert(i, 1);
    }
    

    With that, the code compiles fine.