If I create a class:
// First Example
#include <iostream>
#include <string>
class my_class {
std::string str;
public:
my_class(const char* s = "") : str(s) {}
operator const char* () const { return str.data(); } // accessor
};
my_class mc1{"abc"};
std::cout << mc1; // Calls the char* accessor and successfully writes "abc" to screen output.
If I modify the class thus:
// Second Example
class my_class {
std::string str;
public:
my_class(const char* s = "") : str(s) {}
operator std::string () const { return str; } // accessor
};
my_class mc1{"abc"};
std::string mystring = mc1; // Calls the string accessor
std::cout << mystring; // Also successfully writes "abc" to screen output.
However, if I try to call:
std::cout << mc1;
I will get a page full of compilation errors that begin with:
error C2679: binary '<<': no operator found which takes a right-hand operand of type 'my_class' (or there is no acceptable conversion)
I can correct this error by adding to the second example class:
friend std::ostream& operator <<(std::ostream& os, my_class& rhs) {
os << rhs.str;
return os;
}
which I mostly cribbed from one of the suggested solutions to this problem. But I don't understand why it is necessary using a string accessor but not a char* accessor.
I was expecting successful compilation and output of the value of mc1.str, OR I would have expected the same error trying to use the char* accessor function in the fist example. Instead I received C2679 on the second example only.
UPDATE: I see that using a cast operator in the ostream, e.g. std::cout << (std::string)mc1;
, will explicitly invoke the string accessor and write the string to the screen.
CodePudding user response:
This happens because of how the functions are defined. For the const char*
case the function cout
has for that is declared as
template< class CharT, class Traits >
basic_ostream<CharT, Traits>&
operator<<( basic_ostream<CharT, Traits>& os, const char* s );
so when it analyzes std::cout << mc1;
it can deduce what CharT
and Traits
from cout
and it finds my_class::operator const char* ()
to convert mc1
into a const char*
so overload resolution is successful and the code compiles.
When you switch to having operator std::string ()
and use std::cout << mc1;
you now need to call the std::string
overload for operator <<
which is declared as
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 );
In this overload not only does the first parameter rely on the template parameters but so does the second parameter. This means the compiler is going to try and deduce the the types of CharT
, Traits
and Allocator
from mc1
directly. No conversion operators are considered durning this step and since mc1
is not actually a std::string
deduction fails and you are left with no possible overloads so the code fails to compile.