22

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)?

uwedolinsky
  • 611
  • 3
  • 8
  • The issue seems to be restricted to overload resolution on the surrogate call function; when resolving for `void foo( void(*)() ); foo(p);`, clang++ unambiguously selects the first conversion function: [live example](http://coliru.stacked-crooked.com/a/be4262978e03da27) (Which is weird considering [over.call.object]/4.) – dyp Mar 07 '14 at 21:36
  • Which version of gcc did you use? I had a related issues after upgrading from gcc46 to gcc47, a situation similar to yours but on a overloaded template function that previously compiled then caused an error until I renamed one of them. I also had a lot more warnings that needed addressing after the upgrade. – 6EQUJ5 Mar 08 '14 at 01:55
  • @andrew-mcdonnell it was gcc 4.8.2. I have also edited the question to include the version numbers of the other compilers I tried. – uwedolinsky Mar 08 '14 at 21:59
  • Very similar to http://stackoverflow.com/q/22064519/476681. I think gcc and msvc are correct. – BЈовић Mar 12 '14 at 13:47

1 Answers1

7

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.

Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157
  • Not sure... the various conditions you omit from the standard include "where _cv-qualifier_ is the same cv-qualification as, or a greater cv-qualification than, _cv_", being _cv_ the cv-qualification of the type of the object. And since object in the OP is not `const`, I think that the overload #2 does not comply and should not be considered. – rodrigo Mar 12 '14 at 13:43
  • 1
    `cv` is the qualification of the object expression and is empty. `cv-qualifier` is the qualification of the conversion function and is empty in one case (the same as), and `const` in the other (greater). Both are valid. – Sebastian Redl Mar 12 '14 at 13:56
  • Hmm there's a note in [over.call.object]/4 that says "The conversion function from which the surrogate call function was derived will be used in the conversion sequence for that parameter [the implied object parameter] since it converts the implied object argument to the appropriate function pointer or reference required by that first parameter." Is this wrong then? – dyp Mar 12 '14 at 14:21
  • 1
    Yes, that note simply appears to be wrong. Since it's just a note, it can probably be filed as an editorial defect. – Sebastian Redl Mar 12 '14 at 14:39
  • That footnote 129 about the ambiguity is referenced by the sentence (in clause 13.3.1.1.2.2 ) that talks about considering conversion functions declared in a base class, but there's no base class in the example. – uwedolinsky Mar 18 '14 at 10:39
  • I interpret the footnote (128 in the final C++11) to apply to the paragraph as a whole. There isn't actually a way to use base classes to achieve identical signatures in the construction of surrogates because that would be a case where the base's conversion function is hidden, though it is possible to create the case where surrogates only differ in return types. – Sebastian Redl Mar 18 '14 at 11:09
  • Thanks for your answer (the bounty really was worth it ;) Have you filed a bug report for gcc, MSVC or the note in [over.call.object]/4? – dyp Mar 18 '14 at 13:43
  • No, I haven't. Do you want to do it or should I? – Sebastian Redl Mar 18 '14 at 14:48
  • Well you came up with the answer (btw I didn't get notified of your comment) so I'll leave it up to you ;) If you don't *want* to, I'll do it, at least reporting the note which bugs me. – dyp Mar 18 '14 at 17:11
  • I'm not particularly eager to do it. – Sebastian Redl Mar 19 '14 at 11:09
  • Hmm, doesn't this contradict the note in clause 4 of 13.3.1.1.2? "When comparing the call against a surrogate call function, the implied object argument is compared against the first parameter of the surrogate call function. The conversion function from which the surrogate call function was derived will be used in the conversion sequence for that parameter since it converts the implied object argument to the appropriate function pointer or reference required by that first parameter." – JSQuareD Dec 05 '20 at 23:48