简体   繁体   中英

C++ ambiguous call in function overloading

Just a curiosity (academic question :P), consider the following code:

struct Y {};

class PassConstY {
public:
    PassConstY(const Y& y) {}
};

class PassY {
public:
    PassY(Y& y) {}
};

void z(PassConstY y) {}
void z(PassY y) {}

void h(const Y& y) {}
void h(Y& y) {}

Y y;
const Y cy;
h(cy); //OK
h(y); //OK
z(cy); //OK
z(y); //Ambiguity

Question: is it possible to write PassY and PassConstY st

  1. z and h obeys exactly the same overloading rules
  2. if I remove the z and h definitions for the mutable Y the code still compiles (ie I can call z and h with the mutable version too).

My guess is no, in the sense that I managed to have PassConstY constructable from a const Y only (and not a mutable Y ), which removes the ambiguity, but then point 2 is doomed to failure.


Clarifications:

PassY and PassConstY can be defined as you prefer (but must be classes, may be templated) but the definitions of z and h must be unchanged.

The following code must compile when only the "const" versions of z and h are defined:

const Y y;
z(y); //Calls z(PassConstY y)
h(y); //Calls h(const Y& y)

Y x;
z(x); //Calls z(PassConstY y)
h(x); //Calls h(const Y& y)

The following code must compile when only the "mutable" versions of z and h are defined:

Y x;
z(x); //Calls z(PassY y)
h(x); //Calls h(Y& y)

And the following code must compile when both versions of h and z are defined:

const Y y;
z(y); //Calls z(PassConstY y)
h(y); //Calls h(const Y& y)

Y x;
z(x); //Calls z(PassY y)
h(x); //Calls h(Y& y)

Sorry if the original question was not clear enough!


Motivations (really another question of mine here on Stackoverflow):

Regaring the "disable r-value binding" in the comment below, I would like to come up with a template class Ref (think of it like a smart reference) that I can use when I write function definitions like:

struct X {};

void f(Ref<X> x) {} //Instead of void f(X& x)
void f(Ref<const X> x) {} //Instead of void f(const X& x)

so that I get the same behaviour when calling the function f (in terms of overloading resolution/conversions) as for the plain reference versions, but Ref never binds to rvalues.

[For this part, C++0X is needed]

The reason of this is:

  • A SCS 1 is better than a SCS 2 if, among other things, SCS 1 binds a reference to const, and SCS 2 binds a reference to non-const.
  • A UCS 1 is better than a UCS 2 if and only if, among other things, both use the same conversion function or constructor.

For h , which uses a SCS, the first bullet will figure out a winner for the case where both functions are viable candidates (second call). For z , the case where both functions are viable candidates (second call) there is no UCS better than the other UCS, because both UCSs use different constructors.

Notice that an UCS is defined as

  • One SCS (converting the argument to the parameter type of the constructor in case a constructor is used, and to TypeOf(*this) & for a conversion operator function)
  • The call to the user defined conversion (constructor or conversion operator function)
  • Another SCS (converts the result of the conversion to the final destination type).

You cannot formate a SCS for "const Y to Y&", so there exist no first SCS for the UCS for the second h function, and so it is not a viable candidate. This is not true for the second call to h though, and it thus results in the ambiguity described above.


I've shortened some terms:

UCS : User-defined Conversion Sequence
SCS : Standard Conversion Sequence


My guess is no, in the sense that I managed to have PassConstY constructable from a const Y only (and not a mutable Y), which removes the ambiguity, but then point 2 is doomed to failure.

That suspicion of yours, if I understand you correctly, is not true. With your current definitions, you can remove the mutable versions of both h and z and your code will compile fine. There is an SCS for Y to const Y& .


You can, of course, rewrite PassY and PassConstY as

typedef Y &PassY;
typedef Y const& PassConstY;

Other than that, I'm not sure what you are heading at when you say "rewrite". If you want to just change the class body, then definitely overload resolution will be different.

I'm not sure if this may help you but you could write wrapper for z that uses templates:

template <typename T>
struct TypeFor {
    typedef PassY type;
};

template <typename T>
struct TypeFor<const T> {
    typedef PassConstY type;
};

template <typename T>
void  wrap_z(T& y) { z(static_cast<typename TypeFor<T>::type>(y)); }

Now depending on your calling argument type, T will become either Y or const Y and the correct explicit conversion will be used.

This uses the fact that structs (but not functions) can be partially specialized, which we have done here for different constness of T .

z(y) is ambiguous because there is an implicit conversion in between and it does not know which one.

If you used explicit constructors to PassY and PassConstY then both z's would fail.

You could also make Pass a template with an implicit constructor:

template< typename T > 
class Pass
{
   Pass( T& t ) {}
};

typedef Pass<Y> PassY;
typedef Pass<const Y> PassConstY;

I am still not sure this would solve your problem though but you could also make za template function:

template< typename T> void z( Pass<T> t );

although in real code I would avoid the implicit conversion and use a function

template<typename T> Pass<T> pass(T& t) { return Pass<T>(t); }

Now above is a partial-specialisation and you can also define

template< typename T> void z( T& t) { z( pass(t) ); }

and it should work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM