Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected ambiguity of surrogate call functions in C++

Tags:

On the following code clang and EDG diagnose an ambiguous function call, while gcc and Visual Studio accept the code.

struct s {     typedef void(*F)();     operator F();       //#1     operator F() const; //#2 };  void test(s& p) {     p(); //ambiguous function call with clang/EDG; gcc/VS call #1 } 

According to the C++ standard draft (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3797.pdf) section 13.3.1.1.2 2 says;

a surrogate call function with the unique name call-function and having the form R call-function ( conversion-type-id F, P1 a1, ... ,Pn an) { return F (a1,... ,an); } is also considered as a candidate function.

In the code above that seems to mean that two call function definitions (one for each conversion function) are being considered, but both call functions have identical signatures (therefore the ambiguity) since the cv-qualifiers of the conversion operator do not seem to be taken into account in the call function signature.

I would have expected #1 to be called as with gcc and Visual Studio. So if clang/EDG are instead right in rejecting the above code, could someone please shed some light on the reason as to why the standard stipulates that there should be an ambiguity in this case and which code benefits from that property of surrogate call functions? Who is right: clang(3.5)/EDG(310) or gcc (4.8.2)/VS(2013)?

like image 791
uwedolinsky Avatar asked Mar 07 '14 19:03

uwedolinsky


1 Answers

Clang and EDG are right.

Here's how this works. The standard says (same source as your quote):

In addition, for each non-explicit conversion function declared in T of the form

operator conversion-type-id () attribute-specifier-seq[opt] cv-qualifier ; 

where [various conditions fulfilled in your example], a surrogate call function with the unique name call-function and having the form

R call-function ( conversion-type-id F, P1 a1, ... ,Pn an) { return F (a1, ... ,an); } 

is also considered as a candidate function. [do the same for inherited conversions]^128

And the footnote points out that this may yield multiple surrogates with undistinguishable signatures, and if those aren't displaced by clearly better candidates, the call is ambiguous.

Following this scheme, your type has two conversion operators, yielding two surrogates:

// for operator F(); void call-function-1(void (*F)()) { return F(); } // for operator F() const; void call-function-2(void (*F)()) { return F(); } 

Your example does not contain any other candidates.

The compiler then does overload resolution. Because the signatures of the two call functions are identical, it will use the same conversion sequence for both - in particular, it will use the non-const overload of the conversion function in both cases! So the two functions cannot be distinguished, and the call is ambiguous.

The key to understanding this is that the conversion that is actually used in passing the object to the surrogate doesn't have to use the conversion function the surrogate was generated for!

I can see two ways GCC and MSVC may arrive at the wrong answer here.

Option 1 is that they see the two surrogates with identical signatures, and somehow meld them into one.

Option 2, more likely, is that they thought, "hey, we don't need to do the expensive searching for a conversion for the object here, we already know that it will use the conversion function that the surrogate was generated for". It seems like a sounds optimization, except in this edge case, where this assumption is wrong. Anyway, by tying the conversion to the source conversion function, one of the surrogates uses identity-user-identity as the conversion sequence for the object, while the other uses const-user-identity, making it worse.

like image 67
Sebastian Redl Avatar answered Sep 29 '22 16:09

Sebastian Redl