The selected overload from the conversion functions set is not consistent between GCC and Clang
Asked Answered
M

0

8

Consider this example

#include <iostream>
struct T{
    T() = default;
    T(T const&)  =default;
};
T global;
struct S{
    operator T(){
        std::cout<<"#1\n";
        return T{};
    }
    operator T&(){
      std::cout<<"#2\n";  
      return global;
    }
};

int main(){
   S s;
   T obj(s);
}

GCC selects #1 while Clang selects #2, instead, I consider them ambiguous. According to dcl.init#17.6.3

Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversions that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in [over.match.copy], and the best one is chosen through overload resolution ([over.match]). If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The function selected is called with the initializer expression as its argument; if the function is a constructor, the call is a prvalue of the cv-unqualified version of the destination type whose result object is initialized by the constructor. The call is used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization.

According to over.match.copy#1.2

When the type of the initializer expression is a class type “cv S”, the non-explicit conversion functions of S and its base classes are considered. When initializing a temporary object ([class.mem]) to be bound to the first parameter of a constructor where the parameter is of type “reference to cv2 T” and the constructor is called with a single argument in the context of direct-initialization of an object of type “cv3 T”, explicit conversion functions are also considered. Those that are not hidden within S and yield a type whose cv-unqualified version is the same type as T or is a derived class thereof are candidate functions. A call to a conversion function returning “reference to X” is a glvalue of type X, and such a conversion function is therefore considered to yield X for this process of selecting candidate functions.

Hence, regardless of #1 or #2, they are both considered to yield type T. They both have the implicit parameter object with T&, hence [over.match.best#2.1] cannot determine which is the best

for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that,

The only hope that can determine which overload is the best falls on [over.match.best#2.2]

the context is an initialization by user-defined conversion (see [dcl.init], [over.match.conv], and [over.match.ref]) and the standard conversion sequence from the return type of F1 to the destination type (i.e., the type of the entity being initialized) is a better conversion sequence than the standard conversion sequence from the return type of F2 to the destination type

Their standard conversion sequences are both identity conversions. Hence, they are should be ambiguous. However, GCC and Clang have their respective interpretations and are not consistent. I wonder which interpretation is correct?

Misstep answered 6/9, 2021 at 9:25 Comment(5)
This may relate to the "oversight of the wording" pointed out by R. Smith in CWG 2327 (still in status drafting), particularly "We should presumably be simultaneously considering both constructors and conversion functions in this case, as we would for copy-initialization, but we'll need to make sure that doesn't introduce any novel problems or ambiguities.". The example shown in CWG 2327 would seem to argue that picking #1 via [dcl.init]/17.6.3 recursively falls into /17.6.2, on the temporary resulting from #1.Covalence
I agree that it seems to be ambiguity in the wording here, but I also wonder if [dcl.init.ref]/5.1.1 taking precedence over [dcl.init.ref]/5.3.2 for the initialization of the reference in the copy-constructor to T is if essence here? Particularly if we consider a two-step approach, were the destination type of the first step is the type of the copy ctor of T, and then going via [dcl.init]/17.2 into [dcl.init.ref]. This would argue for Clang being correct.Covalence
@Covalence For the second comment, My opinion is, I don't think the constructor of T would affect the overload resolution in [dcl.init#17.6.3], since it says The call is used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization., In this step, it will involve the constructor call. overload resolution takes place prior to that direct-initialization.Misstep
@Covalence [over.match.best#2.3] is totally about reference to function type.Misstep
I posted later independently a related question #69149290. Thanks for @Covalence who found it. Here the move constructor of T is prohibited, but if one enables it then GCC starts complaining about ambiguity, while Clang does not: godbolt.org/z/ncYd9qTrs This is another point of interest.Areola

© 2022 - 2024 — McMap. All rights reserved.