The Great Java
The Great Java

Reputation: 153

C++ implicit conversions in operands to standard operators

I am having some trouble understanding the conditions under which C++ will use an implicit conversion. Suppose I have a class:

class SomeClass
{
private:
    int val;
public:
    SomeClass(int i) :val{ i } {}
    operator string() { return to_string(val); }
};

Why is it that when using this class with operators I need to cast to string? Why doesn't it just perform the conversion implicitly?

code:

int main(void)
{
    SomeClass sc = 4;
    cout << (string)sc << endl; //prints 4, compiles fine
    cout << sc << endl;//does not compile, no operator "<<" matches these operands

    string str1 = sc;//compiles fine, performs cast
    string str2 = "Base String" + sc;//does not compile, no operator "+" matches these operands
}

This question is more academic than practical as just using a cast is more readable anyway.

Upvotes: 5

Views: 129

Answers (2)

skypjack
skypjack

Reputation: 50550

std::cout doesn't accept a std::string.
It accepts a templated std::basic_string, the parameters of which are to be deduced.
See here for further details.

Let's consider the following example:

#include<string>

struct SomeClass {
    operator std::string() { return ""; }
};

template <class CharT, class Traits, class Allocator>
void f(const std::basic_string<CharT, Traits, Allocator> &) {}

template <class CharT, class Traits, class Allocator>
void f(std::basic_string<CharT, Traits, Allocator> &) {}

int main(void) {
    SomeClass sc{};
    f((std::string)sc);
    //f(sc);
}

It is similar to what happens in your case.
Of course, f(sc) doesn't compile for almost the same reason.

The problem is that to deduce the template parameters SomeClass should be converted to a std::string. To be able to convert it to a std::string, the compiler should guess that a valid overload exists and try to use it. Anyway, such a viable function doesn't exist from its point of view, for template parameters are still to be deduced and to deduce them the compiler should guess that an overload for SomeClass exists after a cast. It can't, because... And so on.

I haven't checked it, but I would bet that something similar applies to operator+ too.

Upvotes: 2

user743382
user743382

Reputation:

The operator<< overload that gets used when you write cout << (string)sc is a function template:

template <class CharT, class Traits, class Allocator>
std::basic_ostream<CharT, Traits>&
    operator<<(std::basic_ostream<CharT, Traits>& os,
               const std::basic_string<CharT, Traits, Allocator>& str);

This is because std::string is itself a class template, and can be instantiated with other types than char, and should still be printable if the characters are written to a stream of that same character type. In fact, that's exactly what happens with std::wstring, where CharT is wchar_t rather than char. That same operator<< works, provided you use a suitable stream.

When you write cout << sc, that particular overload is considered. That overload is then rejected because the compiler cannot deduce which type to use for CharT and the others. It would only be able to deduce the type if it already knew that the conversion would be performed, but the conversions are not considered just yet, they would only be checked in a slightly later stage.

If there were a non-template operator<< overload that took std::string, then it would work, sc would be converted implicitly.

Upvotes: 2

Related Questions