Home > Back-end >  Using std string accessor with ostream operator <<
Using std string accessor with ostream operator <<

Time:11-15

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.

  • Related