Question

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?

Was it helpful?

Solution

The problem is due to the fact std::string is actually an instance of the class template std::basic_string. An operator== that is available in namespace std takes two std::basic_string templates:


template<class charT, class traits, class Allocator>
bool operator==(const basic_string& lhs,
                const basic_string& rhs);

If this version of operator== was overloaded specifically on std::string, your code would be fine. But that's not the case, which would require the compiler to perform template argument deduction on the template parameters of std::basic_string so it could understand that the return of your conversion operator is a possible match.

However, the compiler won't do that. I don't know which part of the standard states this precisely. But the general idea is that such conversions work only for non-template types.

One thing I can suggest is for you to place StdStringConverter in a namespace and provide a version of operator== for std::string in that namespace. This way, when your compiler find an expression like that ADL (Argument Dependent Lookup) comes into play and everything works fine.


#include <string>

namespace n1 {

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

bool operator==(std::string const& a, std::string const& b)
{
  return a == b; //EDIT: See Paul's comment on std::operator== here.
}

}

int main()
{
    using namespace n1;
    StdStringConverter converter(std::string("Me"));
    std::string name = "Me";
    return (converter == name) ? 0 : 1;   
}

OTHER TIPS

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.

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

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top