Search code examples
c++language-lawyeroverloading

Overload resolution with const-qualification


Lets consider simple reduced example

struct Foo {};
struct Bar : Foo {};
struct Baz : Foo {};

struct X {
  operator Bar() { std::cout << "Bar\n"; return {}; }
  operator Baz() const { std::cout << "Baz\n"; return {}; }
};

void foo(const Foo &f) {}

int main() {
  foo(X{});
}

Here both GCC and clang are printing "Bar", see godbolt example

I am struggling to understand why.

There is no problem to call const-annotated method in rvalue object, see this example

So lets build ICS:

X{} -> user conversion -> Bar -> const Foo&
X{} -> user conversion -> Buz -> const Foo&

Both ICS have 1 user conversion and equivalent standard conversion tail. This shall be ambiguity by [over.ics.rank] C++20.

Can someone help to motivate what happens? I don't believe both mainline compilers agree and wrong here.


Solution

  • foo(X{}) attempts to initialize a parameter of type const Foo& with a prvalue of type X.

    This falls under [dcl.init.ref]/5.3.2, which directs us to [over.match.ref] to select a conversion function through overload resolution.

    Both operator Bar() and operator Baz() const are candidates, but the former is a better match because its object parameter is not const ([over.ics.rank]/3.2.6).

    So, the (only) conversion sequence from X to const Foo& goes through Bar. And we never rank it against another conversion sequence because there is only one foo anyway.


    An ambiguity would arise in a situation like this:

    void foo(Bar);
    void foo(Baz);
    foo(X());
    

    Now the call is ambiguous because the ICS from X to Bar is indistinguishable from X -> Baz, despite its first standard conversion sequence being better.