views:

830

answers:

3

As a learning exercise, I have been looking at how automatic type conversion works in C++. I know that automatic type conversion should generally be avoided, but I'd like to increase my knowledge of C++ by understanding how it works anyway.

I have created a StdStringConverter class that can be automatically converted to a std::string, but the compiler (g++ 4.3.4 on Debian) seems not to do the conversion when the object is compared against a real std::string (please ignore the lack of passing-by-reference and unnecessary creation of temporary objects):

#include <string>

class StdStringConverter
{
public:
    explicit StdStringConverter(std::string name) : m_name(name) {}
    operator const std::string () const { return m_name; }
private:
    std::string m_name;
};

int main()
{
    StdStringConverter converter(std::string("Me"));
    const std::string name = "Me";
    // Next line causes compiler error:
    // no match for 'operator==' in 'converter == name'
    return (converter == name) ? 0 : 1;
}

On the other hand, if I change it slightly to a CStringConverter class, the automatic conversion does take place, although comparing char pointers probably isn't what I intended:

#include <string>

class CStringConverter
{
public:
    explicit CStringConverter(std::string name) : m_name(name) {}
    operator const char* () const { return m_name.c_str(); }
private:
    std::string m_name;
};

int main()
{
    CStringConverter converter(std::string("Me"));
    const char* name = "Me";
    // Next line compiles fine, but they are not equal because the
    // pointers don't match.
    return (converter == name) ? 0 : 1;
}

Is there something special about the difference between a std::string and a char* in this context that makes the compiler not treat them the same?

A: 

There are multiple factors. If you change the return statement thusly

return (std::operator==(name, name)) ? 0 : 1;

it compiles, although it obviously does not do the same thing. On the other hand

return (std::operator==(converter, name)) ? 0 : 1;

does not but provides a more interesting error message

no matching function for call to ‘operator==(StdStringConverter&, const std::string&)

which reminds me that operator== is templated on basic_string<>, which has three template parameters to boot. If you use int in your example rather than std::string, the compiler does not complain.

How to obtain the desired effect with std::string is more intriguing ...

Schwanritter
+1  A: 

In the first example the two compared classes (string and StdStringConverter) do not get any special treatment from the compiler for type converting. That means the operator overload you made doesn't even get triggered. The compiler looks through the list of operator== overloads and non of them take in a StdStringConverter so it yells at you.

In the second example the name is char *. Since it is a primitive type then the compiler attempts to convert the non primitive down to a char *. Since you have an override in place it works and you compare addresses.

The compiler will not explicit type cast on operations that don't include primitive types. Something it will do is attempt to use constructors to make the types match. For example if you change your first example to this:

#include <string>

class StdStringConverter
{
public:
    StdStringConverter(std::string name) : m_name(name) {}
    bool operator==(const StdStringConverter &name) { return m_name == name.m_name; }
    operator const std::string () const { return m_name; }
private:
    std::string m_name;
};

int main()
{
    StdStringConverter converter(std::string("Me"));
    const std::string name = "Me";
    // Next line causes compiler error:
    // no match for 'operator==' in 'converter == name'
    return (converter == name) ? 0 : 1;
}

Now the program returns 0. Since the constructor is now not explicit the compiler will attempt to use it to convert the string to a StdStringConverter. Since there is now an operator== in the StdStringConverter everything works.

resolveaswontfix
This explanation is incorrect. Replace all uses of std::string with struct A { int a; };, give A a global operator==, and it works fine. The problem is not that std::string is a user-defined type, it's the template operator== for basic_string (that is, string doesn't have an operator== of its own, only one deduced from basic_string).
Steve Jessop
So for example, another (silly) way to get it to compile is to define `bool operator==(const std::string }`. Then the conversion to std::string can be performed implicitly, in order to use this operator== taking two strings.
Steve Jessop
+7  A: 
ltcmelo
+1, that's exactly what happens, of course. The paragraphs in the Standard are `14.8.2.1`, which list the possible conversions during argument deduction. A user defined conversion to make deduction succeed isn't allowed, of course. `14.8.1/4` finally allows all implicit conversions to take place if a parameter contains no template parameter to be deduced (anymore).
Johannes Schaub - litb
The foot-note on `14.8.3/1` contains further explanation (note that this is merely informative, not normative): "The set of conversions allowed on deduced arguments is limited, because the argument deduction process produces function templates with parameters that either match the call arguments exactly or differ only in ways that can be bridged by the allowed limited conversions. Non-deduced arguments allow the full range of conversions."
Johannes Schaub - litb
@litb - Thanks for the references :)
ltcmelo
Thanks for the explanation. When I try your code, though, I get a segfault: the debugger says that "a == b" is recursively calling itself. Changing it to "return std::operator==(a, b);" works as expected. Would this be the correct fix (if I actually wanted to do this)?
Paul Stephenson
@Paul Stephenson - Certainly, yes!
ltcmelo