0

I want to compare two sets of objects. For the same code I get 2 different outputs on different machines. The code is compiled with two different compilers. On the x86-64 machine i used gcc-11, on the aarch64 (raspberry pi4) machine i used gcc-8. I have to use the gcc-8 on the raspberry because it's in the official repositories.

Has anyone an idea why this happens? Have I missed something?

#include <iostream>
#include <set>
#include <memory>

class ClassA {
private:
    ::std::string id;
public:
    explicit ClassA(::std::string id);

    [[nodiscard]] ::std::string getId() const;

    bool friend operator<(const ::std::shared_ptr<ClassA> &lhs, const ::std::shared_ptr<ClassA> &rhs);
};

ClassA::ClassA(::std::string id) : id{::std::move(id)} {}

::std::string ClassA::getId() const { return id; }

bool operator<(const ::std::shared_ptr<ClassA> &lhs, const ::std::shared_ptr<ClassA> &rhs) {
    auto r = (lhs->id < rhs->id);
    ::std::cout << ::std::boolalpha << "Comparing lhs->id " << lhs->id << ", with rhs->id " << rhs->id
                << ". The result is " << r << ::std::endl;
    return (lhs->id < rhs->id);
}

class ClassB {
public:
    ::std::set<::std::shared_ptr<ClassA>> members;

    void add(::std::shared_ptr<ClassA> a);
};

void ClassB::add(::std::shared_ptr<ClassA> a) {
    members.emplace(::std::move(a));
}

int main() {
    ::std::cout << "Create first set:" << ::std::endl;
    auto firstContainer = ::std::set<::std::shared_ptr<ClassA>>{::std::make_shared<ClassA>("_3"),
                                                                ::std::make_shared<ClassA>("_5")};
    ::std::cout << "Create second set:" << ::std::endl;
    auto secondContainer = ::std::set<::std::shared_ptr<ClassA>>{::std::make_shared<ClassA>("_5"),
                                                                 ::std::make_shared<ClassA>("_3")};
    auto b1 = ::std::make_shared<ClassB>();
    auto b2 = ::std::make_shared<ClassB>();
    ::std::cout << "Fill first ClassB instance:" << ::std::endl;
    for (const auto &r: firstContainer) {
        b1->add(r);
    }
    ::std::cout << "Fill second ClassB instance:" << ::std::endl;
    for (const auto &r: secondContainer) {
        b2->add(r);
    }
    auto result = ::std::equal(b1->members.begin(), b1->members.end(), b2->members.begin(),
                               [](const ::std::shared_ptr<ClassA> lhs, ::std::shared_ptr<ClassA> rhs) -> bool {
                                   return lhs->getId() == rhs->getId();
                               });
    ::std::cout << ::std::boolalpha << "The result is: " << result << ::std::endl;

    ::std::cout << "First ClassB members" << ::std::endl;
    for (const auto &r: b1->members) {
        ::std::cout << "Id " << r->getId() << ::std::endl;
    }

    ::std::cout << "Second ClassB members" << ::std::endl;
    for (const auto &r: b2->members) {
        ::std::cout << "Id " << r->getId() << ::std::endl;
    }
    return 0;
}

The x86-64 output.

Create first set:
Comparing lhs->id _3, with rhs->id _5. The result is true
Comparing lhs->id _5, with rhs->id _3. The result is false
Create second set:
Comparing lhs->id _5, with rhs->id _3. The result is false
Comparing lhs->id _3, with rhs->id _5. The result is true
Comparing lhs->id _3, with rhs->id _5. The result is true
Fill first ClassB instance:
Comparing lhs->id _5, with rhs->id _3. The result is false
Comparing lhs->id _3, with rhs->id _5. The result is true
Comparing lhs->id _5, with rhs->id _3. The result is false
Fill second ClassB instance:
Comparing lhs->id _5, with rhs->id _3. The result is false
Comparing lhs->id _3, with rhs->id _5. The result is true
Comparing lhs->id _5, with rhs->id _3. The result is false
The result is: true
First ClassB members
Id _3
Id _5
Second ClassB members
Id _3
Id _5

The aarch64 output:

Create first set:
Create second set:
Fill first ClassB instance:
Fill second ClassB instance:
The result is: false
First ClassB members
Id _3
Id _5
Second ClassB members
Id _5
Id _3
jUnG3
  • 3
  • 2
  • 2
    Well, shared_ptr has its own operator<... – numzero Sep 08 '21 at 12:59
  • 1
    You've broken the ODR rule. That makes this ill formed; no diagnostics required. – NathanOliver Sep 08 '21 at 13:01
  • [gcc8 on x86](https://godbolt.org/z/To447dxrq) also shows the aarch64 output. – KamilCuk Sep 08 '21 at 13:02
  • @numzero I am aware of that fact. The thing that I can't understand is why does it work, as I would expect in one case and in the other it don't? – jUnG3 Sep 08 '21 at 13:03
  • In the first case, the compiler/linker decided the function defined in main should be used, in the second one, it decided to use the built in one. How it decides which to use is anyone's guess and is not going to be portable. This is why defining operators for types you do not own is dangerous. It could compile, and even work as expected on your machine, but all bets are off when you go somewhere else. – NathanOliver Sep 08 '21 at 13:06
  • Simple portable solution: Use a specific (and uniquely named) ordering function that you pass to the `std::set` constructor. – Some programmer dude Sep 08 '21 at 13:07
  • @Someprogrammerdude, that's an idea and it helped. – jUnG3 Sep 08 '21 at 13:25

1 Answers1

0

The problem is here:

bool operator<(const ::std::shared_ptr<ClassA> &lhs, const ::std::shared_ptr<ClassA> &rhs)

The shared pointer has its own comparison operators which compares stored pointers. There is also owner_less which compares owned pointers (a specially constructed shared pointer can own one object but point to another, e.g. it can point to a member of the owned object).

If you need to compare the pointed-to objects, you should write a comparator doing just that, and pass it to the set as second template argument. Like:

struct my_ClassA_id_less {
    bool operator() (const ::std::shared_ptr<ClassA> &lhs, const ::std::shared_ptr<ClassA> &rhs) {
        // your comparison code here, as for operator<
    }
};

::std::set<std::shared_ptr<ClassA>, my_ClassA_id_less> my_set;
numzero
  • 2,009
  • 6
  • 6
  • Than you for the solution. That's what I have been looking for. One addition, the operator has to be `const`. – jUnG3 Sep 08 '21 at 13:26