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;
}
...
};