5

Long story short: I'd like to understand why the D::operator B() const conversion operator is not used in the last line in the code below, which thus fails when compiling with g++ -std=c++17 source.cpp (compiling with g++ -std=c++2a deleteme.cpp is successful, though).

The error is:

$ g++ -std=c++17 deleteme.cpp && ./a.out 
In file included from /usr/include/c++/10.2.0/cassert:44,
                 from deleteme.cpp:1:
deleteme.cpp: In function ‘int main()’:
deleteme.cpp:19:14: error: no match for ‘operator==’ (operand types are ‘D’ and ‘B’)
   19 |     assert(d == B{2}); // conversion operator not invoked explicitly errors // LINE
      |            ~ ^~ ~~~~
      |            |    |
      |            D    B
In file included from /usr/include/c++/10.2.0/utility:70,
                 from deleteme.cpp:2:
/usr/include/c++/10.2.0/bits/stl_pair.h:466:5: note: candidate: ‘template<class _T1, class _T2> constexpr bool std::operator==(const std::pair<_T1, _T2>&, const std::pair<_T1, _T2>&)’
  466 |     operator==(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y)
      |     ^~~~~~~~
/usr/include/c++/10.2.0/bits/stl_pair.h:466:5: note:   template argument deduction/substitution failed:
In file included from /usr/include/c++/10.2.0/cassert:44,
                 from deleteme.cpp:1:
deleteme.cpp:19:20: note:   ‘B’ is not derived from ‘const std::pair<_T1, _T2>’
   19 |     assert(d == B{2}); // conversion operator not invoked explicitly errors // LINE
      |       

The code is:

#include <cassert>
#include <utility>

struct B {
    int x;
    B(int x) : x(x) {}
    bool operator==(B const& other) const { return x == other.x; }
};

struct D : std::pair<B,char*> {
    operator B() const { return this->first; }
};

int main() {
    B b{1};
    D d{std::pair<B,char*>(B{2},(char*)"hello")};

    assert((B)d == B{2}); // conversion operator invoked explicitly is fine
    assert(d == B{2}); // conversion operator not invoked explicitly errors // LINE
}

This question is a follow up to this. There I got help to write a class Recursive which behaves like a pair (so inherits from it) whose first is a std::array and whose second is a boost::hana::optional<std::vector<...>> (see the link for details).

Since the second of the std::pair is kind of an "advanced" information to what's contained in the first, in many places I'd like to cast/convert this object of std::pair-like class Recursive to the type of its first.

Enlico
  • 23,259
  • 6
  • 48
  • 102
  • @Holy http://coliru.stacked-crooked.com/a/68d497240770629f – πάντα ῥεῖ Sep 29 '20 at 21:00
  • Why d should be converted to B and not B{2} to D? – mfnx Sep 29 '20 at 21:04
  • @mfnx, because I can slice away what makes `D` different from `B`, leveraging the fact that it has a `B` and something else (a `char*` for instance, I've slightly modified the code). Converting a `B` to a `D` would mean "inventing" a way to fill the `second` element of the `pair`. – Enlico Sep 29 '20 at 21:12
  • I mean, how does the compiler know you want to convert d to type B and then compare (B)d with B{2} if you don't make that clear to the compiler? – mfnx Sep 29 '20 at 21:24
  • 1
    @mfnx, well, since I provide one function to convert from `D` to `B` and no function to convert from `B` to `D`, the choice for me seems pretty easy. Why is the C++17 compiler so reluctant? – Enlico Sep 29 '20 at 21:26
  • 1
    Your intuition is reasonable. And that is what will happen from c++20. – cigien Sep 29 '20 at 21:38

1 Answers1

4

When compiler sees d == B{2}, it first creates a list of operator== overloads that it's able to find, and then performs overload resolution on them.

As the link explains, the overload list contains:

  • Member operator==s of the first operand, if any.
  • Non-member operators==s found by unqualified lookup, if any.
  • Built-in operator==s, if your operands can be converted to built-in types.

There's no mention of examining conversion operators of the first operand, so your operator== doesn't get found.

The solution is to make the operator== non-member (possibly define it as a friend). This works:

friend bool operator==(const B &a, const B &b) {return a.x == b.x;}

Starting from C++20, the rules of comparison operators got relaxed: the compiler will look for member operator== in the second operand as well, and will happily call non-member operator== with arguments in a wrong order.

So a == b and b == a are now equivalent to a degree, except that:

  • An operator== called in this new manner must return bool.
  • If given choice, the compiler will prefer the old ways of calling operator== over calling the member operator== of the rhs, which is in turn preferred over calling a non-member operator== with a wrong argument order.
HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • 1
    The answer is correct, but the last statement "... arguments in a wrong order" is a bit off. `==` *should* have always been symmetric, even as a member, so there is no *wrong* order. c++20 just fixes this hole. So "wrong order" should just be "any order" :) – cigien Sep 29 '20 at 22:04
  • @cigien Turns out they're not entirely equivalent, so in my edit I still have to differentiate between the original and the reversed order. I could've called it "reverse", but eh. :P – HolyBlackCat Sep 29 '20 at 22:14