C++ addition overload ambiguity

I am coming up against a vexing conundrum in my code base. I can't quite tell why my code generates this error, but (for example) std::string does not.

class String {
public:
    String(const char*str);
    friend String operator+ ( const String& lval, const char *rval );
    friend String operator+ ( const char *lval, const String& rval );
    String operator+ ( const String& rval );
};

The implementation of these is easy enough to imagine on your own.

My driver program contains the following:

String result, lval("left side "), rval("of string");
char lv[] = "right side ", rv[] = "of string";
result = lv + rval;
printf(result);
result = (lval + rv);
printf(result);

Which generates the following error in gcc 4.1.2:

driver.cpp:25: error: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second:
String.h:22: note: candidate 1: String operator+(const String&, const char*)
String.h:24: note: candidate 2: String String::operator+(const String&)

So far so good, right? Sadly, my String(const char *str) constructor is so handy to have as an implicit constructor, that using the explicit keyword to solve this would just cause a different pile of problems.

Moreover... std::string doesn't have to resort to this, and I can't figure out why. For example, in basic_string.h, they are declared as follows:

template<typename _CharT, typename _Traits, typename _Alloc>
basic_string<_CharT, _Traits, _Alloc>
operator+(const basic_string<_CharT, _Traits, _Alloc>& __lhs,
          const basic_string<_CharT, _Traits, _Alloc>& __rhs)

template<typename _CharT, typename _Traits, typename _Alloc>
basic_string<_CharT,_Traits,_Alloc>
operator+(const _CharT* __lhs,
          const basic_string<_CharT,_Traits,_Alloc>& __rhs);

and so on. The basic_string constructor is not declared explicit. How does this not cause the same error I'm getting, and how can I achieve the same behavior??

Answers


The reason for the ambiguity is that one candidate function is better than another candidate function only if none of its parameters are a worse match than the parameters of the other. Consider your two functions:

friend String operator+(const String&, const char*); // (a)
String operator+(const String&);                     // (b)

You are calling operator+ with a String and a const char*.

The second argument, of type const char*, clearly matches (a) better than (b). It is an exact match for (a), but a user-defined conversion is required for (b).

Therefore, in order for there to be an ambiguity, the first argument must match (b) better than (a).

The String on the left-hand side of the call to operator+ is not const. Therefore, it matches (b), which is a non-const member function, better than (a), which takes a const String&.

Therefore, any of the following solutions would remove the ambiguity:

  • Change the member operator+ to be a const member function
  • Change the non-member operator+ to take a String& instead of a const String&
  • Call operator+ with a const String on the left hand side

Obviously, the first, also suggested by UncleBens, is the best way to go.


It is sufficient in this case just to define on operator+:

String operator+(const String& lval, const String& rval);

Because you provide a constructor taking a char*, a String can be constructed from a char* during the call to operator+. For example:

String hello = "Hello, ";
const char* world = "world!";

String helloWorld = hello + world;

A temporary String will be constructed with the contents of the char* world (because your constructor is not explicit), then the two String objects will be passed to operator+.


The error goes away if you declare the member + const as it should be.

class String {
public:
    String(const char*str);
    friend String operator+ ( const String& lval, const char *rval );
    friend String operator+ ( const char *lval, const String& rval );
    String operator+ ( const String& rval ) const; //<-- here
};

Not sure what's the reason, though. Perhaps it prefers binding arguments to const reference if possible, so the first overload is a better match for the left-hand value and the third overload has a better match for the right-hand value.

Better explanation. (Must have misread the problem a bit.)


printf(result);

Don't tell me your String has implicit conversion to const char*... That's evil.


Template and non-template functions follow different rules. The template functions are selected on the actual parameter types, without any conversions being applied. In the case of the non-template (i.e. your code) an implicit conversion can be applied. Thus the templated stuff in basic_string is not ambiguous, but yours is.


You've shown that basic_string has implementations of operator+ corresponding to the second and third operators in your class String. Does basic_string also have an operator corresponding to your first operator [friend String operator+ ( const String& lval, const char *rval );]?

What happens if you remove this operator?


Need Your Help

Error accents rest django framework

json django django-rest-framework

I have the problem that it is not recognizing the accents in the rest Json returned with django restframework

Dynamic SQL not working as expected

sql oracle plsql

create or replace procedure createtables