0
votes

I have a class that derived from std::ostringstream, and coded a stream-output liked operator<< friend-func, so that I have a chance to pre-process something before the real stream-output taking place. But if the second operand of << is a object of std::map(or unordered_map), my codes can not go through the compilation with GCC, while second operand that is of many other types can be accepted. I tried int, string, c-string, vector, etc... and all of them are OK but map and unordered_map.

Here is a minimal reproducible example:

#include <iomanip>
#include <iostream>
#include <map>
#include <ostream>
#include <sstream>
#include <string_view>
#include <vector>
#include <unordered_map>

using namespace std;

enum class LogLevel { Debug, Infor, Notif, Warnn, Error, Fatal };
constexpr string_view LevelNames[] { "Debug", "Infor", "Notif", "Warnn", "Error", "Fatal" };
LogLevel sysLogLevel { LogLevel::Debug };

class Logger : public ostringstream {
public:
   Logger( LogLevel lv ) : logLevel( lv ) {};

   ~Logger() override {
      cout << LevelNames[static_cast<int>( logLevel )] << ':' << str() << '\n';
   };

   LogLevel logLevel;
};

template <typename T>
inline Logger& operator<<( Logger& lg, const T& body ) {
   if ( lg.logLevel >= sysLogLevel )
      static_cast<std::ostream&>( lg ) << body;
   return lg;
};

using StrStrMap = map<string, string>;
inline ostream& operator<<( ostream& os, const StrStrMap& ssm ) {
   os << '{';
   for ( const auto& [k,v] : ssm )
      os << k << ':' << v << ',';
   os << '}';
   return os;
};

int main() {
   StrStrMap ssm1 { { "Flower", "Red" }, { "Grass", "Green" } };

   cout << ssm1 << endl;                              // OK!

   { Logger log { LogLevel::Fatal }; log << ssum1; }  // OK!

   Logger(LogLevel::Infor) << ssm1;                   // Compiling Error!

   return EXIT_SUCCESS;
};

The error message of GCC is:

/usr/include/c++/8/ostream:656:11: error: no match for operator<< (operand types are std::basic_ostream<char> and const std::unordered_map<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >)

As pointed by @n.m. , it looks like "Can not bind a lvalue refference to a temporary obj".

But why it can be done when the second operand is of other types such as int, string, c-string, vector and etc?

Here is my attempt with other types:

template<typename T>
class MyIntTemplate {
public:
   MyIntTemplate( T p ) : payLoad(p) {};
   T payLoad;
};
using MyInt = MyIntTemplate<int>;
inline ostream& operator<<( ostream& os, const MyInt& mi ) {
   os << mi.payLoad;
   return os;
};

using StrVec = vector<string>;
inline ostream& operator<<( ostream& os, const StrVec& sv ) {
   os << '{';
   for ( const auto& v : sv )
      os << v << ',';
   os << '}';
   return os;
};

int main() {
   Logger(LogLevel::Infor) << MyInt(123);                   // OK!
   Logger(LogLevel::Warnn) << 456;                          // OK!
   Logger(LogLevel::Debug) << "a Debugging Log";            // OK!
   Logger(LogLevel::Infor) << string( "a Informing Log" );  // OK!

   StrVec sv1 { "Flower", "Grass" };
   Logger(LogLevel::Fatal) << sv1;                          // OK!

   return EXIT_SUCCESS;
};

Because another reason, I really need the logger to be temorary. Would anyone pls give me a solution? Any hints will be appretiated!

2
Shouldn't that be inline Logger& operator<<( Logger& os, const StrStrMap& ssm ) {...}? Plus, is this an academic exercise? Otherwise, it's generally a bad idea to re-implement existing features.Daniel
This statement can be compiled. 'logger << ssm1;' can not.Leon
Please post a minimal reproducible example, it is very unclear which of your code can be compiled and which cannot.n. 1.8e9-where's-my-share m.
@n.m. I posted a example as an answerLeon
Don't post anything that is not an answer as an answer. edit your question instead.n. 1.8e9-where's-my-share m.

2 Answers

0
votes

Logger(LogLevel::Warnn) is a temporary. A non-const reference cannot bind to a temporary.

inline ostream& operator<<( ostream& os,
//                          -------- <------ nope
0
votes

Do what IOStreams does and make an all encompassing overload for rvalues:

template<class T>
Logger& operator<<(Logger&& lg, T const& x) {
  return lg << x;
}