2

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

Nate
  • 12,499
  • 5
  • 45
  • 60

5 Answers5

9

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.

Community
  • 1
  • 1
James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • It didn't even cross my mind that the const-ness would be cause for a bad match. You and UncleBens were (of course) exactly right. One more solution would actually be to make the member a non-member that accepts two const Strings - this is actually why the std::string version has no error. Of course, this is equivalent to your first suggestion, just written differently. – Nate Apr 10 '10 at 20:14
5

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+.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • That creates a useless temporary object, possibly at considerable cost in time and memory. – Daniel Newby Apr 10 '10 at 14:29
  • @Daniel Newby: Not really, as the compiler is able to elude the copy quite often. Also that "considerable cost of time and memory" is exactly what `std::string` does, as asked by the OP in his original post. – Billy ONeal Apr 10 '10 at 16:47
3

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.

Community
  • 1
  • 1
UncleBens
  • 40,819
  • 6
  • 57
  • 90
  • 4
    Hopefully that's a non-standard printf function found through ADL. Although it should be `puts`, not `printf`, if all it does is print the argument. – Ben Voigt Apr 10 '10 at 15:29
  • `puts` adds a new-line, though. I suppose it should be `printf("%s", str.c_str());` if the newline is not wanted. – UncleBens Apr 10 '10 at 15:37
  • 1
    +1 This is arguably the best option. I've tried to explain the ambiguity: http://stackoverflow.com/questions/2613645/c-addition-overload-ambiguity/2614085#2614085 – James McNellis Apr 10 '10 at 16:41
  • Though I know why you're saying that, I think that evil is in the eyes of the beholder ;) – Nate Apr 10 '10 at 20:21
2

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.

1

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?

user200783
  • 13,722
  • 12
  • 69
  • 135