Home > Software design >  Overloading == operator for template results in unknown address error/
Overloading == operator for template results in unknown address error/

Time:12-01

I am trying to overload the == operator for a class Structure, which is a multi-type container. My approach for other operations has been to have an empty virtual function in the Structure class and override it in the derived Element template class.

This is because, when adding the elements to the vector, I am wrapping them into Element and storing them in the vector as StructPtrs. In order to know the type of each StructPtr, I must do the above (I cannot use dynamic_cast) because it will only know its type when it calls on the overridden function in the Element class.

class Structure;
using StructPtr = std::shared_ptr<Structure>;

template<class T>
class Element;

// Make sure to define your printElement function, too.
void printElement(std::ostream& out, const Structure& s);

// Define your `Structure` class here
class Structure{
public:
    Structure() = default;
    virtual ~Structure() = default;

    Structure(const Structure& oldStruct){
        for(auto element : oldStruct.elements)
            elements.emplace_back(element->getCopy());
    }

    Structure operator = (const Structure& otherStruct){
        if(this == &otherStruct)
            return *this;
        if(elements.size() != otherStruct.elements.size()){
            elements.clear();
        }
        std::copy(otherStruct.elements.begin(), otherStruct.elements.end(), std::back_inserter(elements));
        return *this;
    }

    bool operator == (const Structure& other) const{
        if(elements.empty() && other.elements.empty())
            return true;
        bool result = false;
        for(int i = 0; i < other.elements.size(); i  ){
            if(elements[i]->equals(*(other.elements[i])))
                result = true;
            else result = false;
        }
        return result;
    }

    virtual bool equals( Structure& otherStruct) const { }

    template<class T>
    void add(T obj){
        elements.emplace_back(std::make_shared<Element<T>>(obj));
    }

    virtual StructPtr getCopy() const {}

    virtual void printDerived(std::ostream& out) const{}

    void print(std::ostream& out) const{
        for(auto element : elements)
            element->printDerived(out);
    }

    std::vector<StructPtr> elements;
};

template<class T>
class Element : public Structure{
public:
    Element() = default;
    //Element(int element) : element(element) { }
    //Element(Structure element) : element(element) { }
    //Element(std::string element) : element(element) { }
    Element(T _element) : element(_element) { }
    Element( const Element<T>& oldElement){
        element = oldElement.element;
    }

    bool equals ( Structure& otherStruct) const override{
        if constexpr (std::is_same_v<T, Structure>){
            return element == otherStruct; 
        }
        auto other = static_cast<Element&>(otherStruct);
        return element == other.element;   
    }

    StructPtr getCopy() const override {
        StructPtr newStructPtr;
        if constexpr (std::is_same_v<T, Structure>){
            Structure newStruct(*this);
            newStructPtr = std::make_shared<Element<T>>(newStruct);
        }
        else 
            newStructPtr = std::make_shared<Element<T>>(*this);
        
        return newStructPtr;
    }

    void printDerived(std::ostream& out) const override {
        printElement(out, element);
    }

    T element;    
};

void printElement(std::ostream& out, const Structure& s){
    s.print(out);
}

The problem is, for ==, it has a structure in the function argument. This approach does not work, because when I pass this as an argument down to the Element class, it is still a Structure and not an Element.

Right now, my code works for every test case, except for the case of nested structures. When I try to compare two structures with other structures in them, I get this error:

AddressSanitizer:DEADLYSIGNAL
=================================================================
==26776==ERROR: AddressSanitizer: SEGV on unknown address 0x000104fbffb8 (pc 0x00010423ec88 bp 0x00010423ec64 sp 0x00016bbc79d0 T0)
==26776==The signal is caused by a UNKNOWN memory access.

Why is my code resulting in this error, and how do I fix it?

CodePudding user response:

Your operator== is doing the right thing by using a virtual equals() method, however the rest of it is not implemented correctly. You are treating the result variable the wrong way, and worse the code has undefined behavior if the two vectors have different sizes. Try this instead:

class Structure{
public:
    ...
    bool operator == (const Structure& other) const {
        if (elements.size() != other.elements.size())
            return false;
        for(size_t i = 0; i < other.elements.size();   i) {
            if (!elements[i]->equals(*(other.elements[i])))
                return false;
        }
        return true;
    }
    ...
};

Now, that being said, you can't make Element::equals() accept an Element when you are calling it polymorphically from Structure, like you are asking for (at least, not without casting).

But, what you can do (and need to do) is make Element::equals() check if the input Structure is actually an Element or not. You have to do that anyway so that you can differentiate between Element<X> and Element<Y> when X and Y are different types. The way you are using static_cast is not doing that validation, so you will have undefined behavior when you cast to the wrong type. And you can't do the check with std::is_same_v (a compile-time check), either. You must use dynamic_cast (a runtime check), eg:

template<class T>
class Element : public Structure{
public:
    ...
    bool equals (const Structure& otherStruct) const override {
        if constexpr (std::is_same_v<T, Structure>){
            return element == otherStruct; 
        }
        else {
            auto other = dynamic_cast<const Element<T>*>(&otherStruct);
            return (other && element == other->element);
        }
    }
    ...
};

However, since you say you can't use dynamic_cast (why?), you will just have to tag every Element based on which T it is created with, and then you can compare the tags for equality before casting, eg:

#include <typeinfo>

template<class T>
class Element : public Structure{
public:
    ...
    const std::type_info& getTypeInfo() const {
        return typeid(T);
    }
    
    bool equals (const Structure& otherStruct) const override {
        if constexpr (std::is_same_v<T, Structure>){
            return element == otherStruct; 
        }
        else if (getTypeInfo() == otherStruct.getTypeInfo()) {
            return element == static_cast<const Element<T>&>(otherStruct).element;
        }
        else
            return false;
    }
    ...
};
  • Related