4
votes

The following example compiles fine on godbolt with -std=c++17 with clang, but fails with msvc and gcc:

struct Foo
{
};

struct Bar
{
    explicit operator Foo() // w/o explicit qualification all are happy
    {
        return Foo();
    }
};

int main()
{
    Bar b;
    const Foo& foo = static_cast<const Foo&>(b); // only clang happy with this
    const Foo& foo2 = static_cast<const Foo&>(static_cast<Foo>(b)); // clang / msvc / gcc happy with this
    return 0;
}

So as far as I understand the explicit on the operator simply prohibit implicit conversions, and since the page lists qualification conversions among them, I would assume clang is simply wrong in this case? Or am I missing something?

I'd ideally want to stick with the single cast, since I am using this in a templated code, where if the const-reference conversion operator is available it is used. But I could also drop the explicit constraint.

1
I believe foo (if it would compile) and foo2 are dangling references. - bolov
you should do const Foo& foo2 = static_cast<Foo>(b); since this will extend the temporary lifetime to the lifetime of the reference, avoiding the dangling reference I mentioned. Also this is just 1 cast without any modifications to your class. - bolov
@bolov The cast expressions could be used in some other context where lifetime is valid, this is surely just a MRE to house the question about validity of the cast - M.M
@M.M yep :) The example is for the sole purpose to understand if clang is right or not. - Rudolfs Bundulis
@RudolfsBundulis a reference extends only the lifetime of a prvalue. static_cast<const T&> is an lvalue. - bolov

1 Answers

3
votes

OP's example is not really minimal. A truly minimal example uses, instead of a static_cast and then an initialization, simply:

const Foo& foo(b);

This is what static_cast<const Foo&>(b) is supposed to do, anyway.

Here again, Clang accepts and GCC rejects [godbolt]. If this is changed to a copy-initialization (const Foo& foo = b;) then Clang and GCC both reject.

It seems that Clang is correct: in the direct-initialization case, why shouldn't the explicit conversion function be used?

The question is tagged c++11, so see C++11 [over.match.ref]/1:

Under the conditions specified in 8.5.3, a reference can be bound directly to a glvalue or class prvalue that is the result of applying a conversion function to an initializer expression. Overload resolution is used to select the conversion function to be invoked. Assuming that “cv1 T” is the underlying type of the reference being initialized, and “cv S” is the type of the initializer expression, with S a class type, the candidate functions are selected as follows:

  • The conversion functions of S and its base classes are considered, except that for copy-initialization, only the non-explicit conversion functions are considered. Those that are not hidden within S and yield type “lvalue reference to cv2 T2” (when 8.5.3 requires an lvalue result) or “cv2 T2” or “rvalue reference to cv2 T2” (when 8.5.3 requires an rvalue result), where “cv1 T” is reference-compatible (8.5.3) with “cv2 T2”, are candidate functions.

The wording in 8.5.3 was the subject of a defect report which was resolved in C++14 to clarify that the reference binding does not use an "implicit conversion", but instead a "conversion" (where [over.match.ref] excludes the explicit functions only in the copy-initialization case). This resolution should be considered retroactive to C++11.

The wording changes in each edition of the standard but I think the conclusion is the same.

I looked for an existing GCC bug report on this issue, but surprisingly couldn't find one. I think it would be a good idea to open a bug report and, if the GCC devs insist that GCC is correct, they will explain why they think so.