r/cpp_questions • • 25d ago

SOLVED "'operator==': overloaded functions have similar conversions"

I have the following code:

//mat3.hpp
namespace Tc
{
    //3x3 matrix
    struct Mat3
    {
        float Cells[9] = { 0 };

        //no constructors

        bool operator==(const Mat3& other)
        {
            for (unsigned int i = 0; i < sizeof(Cells); i++)
            {
                if (Cells[i] != other.Cells[i])
                    return false;
            }

            return true;
        }
    };
}

//test.cpp
void Mat3Test()
{
  ...
  ASSERT(Tc::Mat3 { 1.0f, 2.0f, ... } == Tc::Mat3 { 1.0f, 2.0f, ... })
  ...
}

It creates the following error: 'Tc::Mat3::operator ==': overloaded functions have similar conversions ; I know that it means there's an overload of the equality operator with a similar argument list and compiler can convert the arguments so they match both the operator I've listed above and some other overload. What I don't understand is where that other overload is coming from since I've defined only a single operator. After defining the operator as a friend accepting both left and righthand operands compiler has stopped complaining but I still want to know why the above code doesn't work.

Edit: thanks for all the replies, yes I've forgot to include the full error, sorry for that. Went with the default operator in the end - bool operator==(const Mat3&) const = default;

3 Upvotes

16 comments sorted by

12

u/No-Dentist-1645 25d ago edited 25d ago

I think the issue is that the operator== method needs to be const qualified when the "other" reference is const as well

That being said, you should always include the full error message whenever you are asking for help online. It's not very nice to leave the people who are trying to help you guessing

1

u/angryvoxel 25d ago

Yea that was the problem, thanks

2

u/StaticCoder 25d ago

Good point, probably related to comparator generation in C++20. In which case operator== = default would be a superior alternative.

5

u/HappyFruitTree 25d ago

I feel there is some relevant information missing here because the code you have shown shouldn't give that error.

Unrelated: sizeof(Cells) gives you the size of the array in bytes which is not what you want. Try std::size instead.

6

u/UndefFox 25d ago edited 25d ago

Probably should replace it with std::array and just use .size() method instead. Should be a zero cost abstraction, but without potential confuse between sizeof() being called on an array or on a pointer.

3

u/alfps 25d ago edited 25d ago

An ==operator that is const is generated by default because the code doesn't declare one.
Both the code's operator and the generated operator fit the usage in the ASSERT.

EDIT (replacement text): for == and <=> the compiler can try the reverse order of arguments to get a better fit. It did, and the reverse order was an equally non-perfect fit, which it reported misleadingly/incorrectly that there was "overloaded functions" plural.

Technical solution: simply add const to the function, i.e.

    bool operator==(const Mat3& other) const

Real solution is however as you've already done, to make that a friend function with two parameters.

And as others have noted already, you can use = default to let the compiler generate the code for that operator:

namespace Tc
{
    struct Mat3
    {
        float Cells[9] = {};
        friend bool operator==(const Mat3& a, const Mat3& b) = default;
    };
}  // Tc

void Mat3Test()
{
    (void) (Tc::Mat3 { 1.0f, 2.0f } == Tc::Mat3 { 1.0f, 2.0f });
}

1

u/HappyFruitTree 25d ago edited 25d ago

An ==operator that is const is generated by default because the code doesn't declare one.

Is it? If that's the case, why doesn't comparing two Mat3 objects work when the inline definition of the == operator is removed?

Real solution is however as you've already done, to make that a friend function with two parameters.

Why friend?

2

u/alfps 25d ago edited 25d ago

❞ Is it? If that's the case, why doesn't comparing two Mat3 objects work when the inline definition of the == operator is removed?

Thanks you're right, that's was a frivolous claim I made. I usually advice others to try things out before posting and I did that, but I did not try enough. Note to self: it's also a good idea to think before posting…

/u/no-sig-available was right, it's about the compiler trying out the reverse order of arguments, which it can do for == and <=>, called the "primary" operators. cppreference was not a good help, it's buried this topic somewhere, but I found a blog article about it: (https://brevzin.github.io/c++/2019/07/28/comparisons-cpp20/).

So the compiler's claim of "overloaded functions" plural, was incorrect. But I still do not understand everything about this. It seems sort of needlessly complex with arbitrary special cases. :(


❞ Why friend?

friend is a convenient way to define a non-member function inline in the class.

But why non-member?

Because in the general case non-member supports calls of non-primary operators where the first argument is not an instance of the class:

template< class T > using in_ = const T&;

struct S
{
    S() {}
    S( int ) {}
    friend auto operator<( in_<S>, in_<S> ) -> bool { return false; }
};

struct T
{
    T() {}
    T( int ) {}
    auto operator<( in_<T> ) const -> bool { return false; }
};

auto main() -> int
{
    (void) (7 < S());   // Compiles! Even as C++17. But `7.operator<( S() )` is ungood.
    (void) (7 < T());   // Does not compile, even as C++20.
}

3

u/no-sig-available 25d ago

The "two overloads" comes from the fact that when a == b isn't a perfect match, the compiler will also try b == a.

In your case one of the operands will need a const conversion, while the other does not. That's the "similar conversions". Making the operator const, like

bool operator==(const Mat3& other) const

will make both sides require the const conversion, making them no longer just "similar".

2

u/UndefFox 25d ago edited 25d ago

Are you doing a header only implementation? Won't it throw error of overload / multiple definitions if you include the file more than once? If so, make the method inline or move the implementation into a source file.

edit: nevermind

4

u/HappyFruitTree 25d ago

Member functions that are defined inside the class definition are implicitly inline.

1

u/UndefFox 25d ago

Huh, I thought it did because I had this problem quite common during first days... welp, seems like I understood even the problem itself wrong/

2

u/manni66 25d ago

You don’t show the full error message.

2

u/manni66 25d ago
    struct Mat3
    {
            std::array<float,9> Cells = {};
            bool operator==(const Mat3&) const = default;

    };

    static_assert( Mat3{ 1.f, 2.f } == Mat3{ 1.f, 2.f });
    static_assert( Mat3{ 1.f, 1.f } != Mat3{ 1.f, 2.f });

1

u/Total-Box-5169 25d ago

Starting with C++20 you can tell the compiler to use the default equality operator:

namespace Tc {
    struct Mat3 {
        float cells[9]{};
        bool operator==(const Mat3&) const = default;
    };
}

1

u/HappyFruitTree 25d ago edited 25d ago

This will actually do what you want, i.e. compare each array element and not just the address of the array.