13

I have some code like the following:

class bar;

class foo
{
public:
    operator bar() const;
};

class bar
{
public:
    bar(const foo& foo);
};

void baz() {
    foo f;
    bar b = f;   // [1]

    const foo f2;
    bar b2 = f2; // [2]
}

GCC gives an error at [2] but not [1]. Clang gives an error on both, and apparently MSVC gives an error on neither. Who's right?

Tavian Barnes
  • 12,477
  • 4
  • 45
  • 118
  • Because in the first case only the bar() constructor will match, in the second both the the bar operator in foo and bar constructor will match the const foo argument. – Rob Aug 11 '17 at 13:33
  • 1
    add `operator bar();` (notice there is no const) and both of them should be ambiguous – Borgleader Aug 11 '17 at 13:34
  • 2
    @tobi303 `bar b = f;` still works fine if only the conversion operator is declared, so it's not exactly true that `foo::operator bar() const` only works on a `const foo`. – Tavian Barnes Aug 11 '17 at 13:35
  • @TavianBarnes well.. when I think about it I realize that my comment was non-sense. Of course a `const` method can be called on a non-const instance... I wonder who upvoted it :P – 463035818_is_not_an_ai Aug 11 '17 at 13:38
  • 1
    I believe it actually should be ambiguous in both cases now. But the relevant part of the standard is extremely complicated so I am not entirely certain. I did observe that clang, and gcc with `-pedantic` flag, both reject `bar b = f;`, but gcc accepts it without `-pedantic` ; so maybe there is a GNU extension in play – M.M Aug 12 '17 at 06:50
  • clang complains on first conversion https://wandbox.org/permlink/fljvSm8gK2PYhCnm gcc on second https://wandbox.org/permlink/bWq4X1h4lQcZD2Qn. – Marek R Aug 12 '17 at 07:08
  • What is more funny visual studio compiler doesn't report an error http://rextester.com/FDX74810 :) – Marek R Aug 12 '17 at 07:30
  • 1
    @MarekR add `-pedantic` flag to second one, technically gcc requires that for ISO compliant behaviour – M.M Aug 13 '17 at 22:29
  • overload resolution involving the cast operators has always been an impenetrable murk to me :) – M.M Aug 13 '17 at 22:30
  • @MarekR: Historical non-conformance in MSVC. Visual Studio 2017's `/permissive-` flag causes them both to be ambiguous, probably due to fixing [Do not treat copy initialization as direct initialization](https://blogs.msdn.microsoft.com/vcblog/2016/11/16/permissive-switch/). [rextester](http://rextester.com) uses Visual Studio 2015, but you can see this failing in Visual Studio 2017 on http://webcompiler.cloudapp.net/. – TBBle Aug 14 '17 at 13:12
  • @TBBie Thanks for the answer! I guess the only remaining question is, what exactly are the semantics of the GCC extension that's in play here? – Tavian Barnes Aug 14 '17 at 13:14
  • Three-way compiler test, all doing the right thing when asked: https://godbolt.org/g/AcUCir – TBBle Aug 14 '17 at 13:18
  • Without -pedantic, g++ selects `bar(const foo&)` over `foo::operator bar() const` for `foo` -> `bar`. It *might* be g++'s definition of `this`, I have a vague recollection that g++ had a non-standard definition of `this` (as an `xvalue &`), not a `const lvalue &`, or something like that. That would worsen `foo::operator bar() const` perhaps, since the initial conversion sequence would not be "Identity". – TBBle Aug 14 '17 at 13:27
  • I tracked down that 'vague recollection': https://stackoverflow.com/a/43013917/166389 – TBBle Aug 17 '17 at 19:10

1 Answers1

2

tl;dr

Ambiguous. (Also, if you're stopping at the tl;dr, then the language-lawyer tag might not be your cup-of-tea. ^_^)

Spoiler

Both candidates have a single const foo& parameter, which binds equally to a const foo or foo argument. No other rules appear that would prefer one or the other function.


Breaking it down against the current C++ working draft

Initializers [dcl.init]

In both cases

Copy-initialization of class by user-defined conversion [over.match.copy]

T is the type being intialised, in both cases this is bar. S is the type of the initializer expression, in the two cases foo and const foo respectively.

  • converting constructors of T are candidates ([over.match.copy]/1.1)
    • bar::bar(const foo& foo); is a candidate
  • the type of the initializer expression is _cv_ S so non-explicit conversion functions are considered: ([over.match.copy]/1.2)
    • foo::operator bar() const is not hidden within foo or within const foo, and yields bar which is the same as T, and hence is a candidate.

So our candidate list is the same in both cases:

  • bar::bar(const foo& foo)
  • foo::operator bar() const

In both cases, we have a user-defined conversion consisting of:

  1. Standard conversion of source type to user-defined conversion argument
  2. User-defined conversion (one of the above two functions) to result type
  3. Standard conversion of result type to target type

If we select the constructor, the "result type" is "a prvalue of the cv-unqualified version of the destination type whose result object is initialized by the constructor" ([dcl.init]/17.6.3), so for both candidate functions, the second Standard Conversion is Identity (bar -> bar).

Overload resolution [over.match]

Subsetting the viable candidate functions [over.match.viable]

Per [dcl.init]/17.6.3, the initializer expression is going to be the argument to the selected call, in the two cases foo and const foo respectively.

bar::bar(const foo& foo)

foo::operator bar() const

Select the best viable function [over.best.ics]

Both are Identity => User Defined Conversion => Identity, i.e., are user-defined conversion sequences.

Ranking conversion sequences over.ics.rank

Can we establish a ranking between the sequences? Only if one of the following applies

  • (X) Not list-initialization sequences ([over.ics.rank]/3)
  • (X) Not a standard conversion sequence ([over.ics.rank]/3.2)
  • (X) The two sequences do not contain "the same user-defined conversion function or constructor or [...] initialize the same class in an aggregate initialization" ([over.ics.rank]/3.3)

Conversion sequences are indistinguishable, i.e., neither is better nor worse

Best viable function over.match.best

Is either function a 'better' function? Only if one of the following applies

Neither is a 'better' function, so the call is ill-formed. [over.match.best]/2

TBBle
  • 1,436
  • 10
  • 27