0

I'm trying to write a simple logging function which can handle different types in this format:

LOG("This is one type: " << one_type << " and here is a different type: " << diff_type);

I've been looking at the examples here:

How to use my logging class like a std C++ stream?

stringstream with recursive variadic function?

and this is what I came up with:

#include <iostream>
#include <sstream>

void LOG(std::stringstream& ss)
{
    std::cout << ss.str() << std::endl;
}

However, even when I just do:

LOG("Some text");

I get the error:

could not convert ‘(const char*)"Some text"’ from ‘const char*’ to ‘std::stringstream’ {aka ‘std::__cxx11::basic_stringstream<char>’}

How can I implement this?

intrigued_66
  • 16,082
  • 51
  • 118
  • 189
  • 1
    stringstream does not have an implicit constructor that takes a string. – 273K Aug 10 '22 at 15:53
  • this looks pretty strange to me. why not use log as stream just like [your linked answer](https://stackoverflow.com/a/511779/5980430) – apple apple Aug 10 '22 at 16:18

2 Answers2

3

A common way to solve this is to make LOG a macro that just does text substitution instead. You could define a LOG macro like

#define LOG(to_log) \
do \
{ \
    std::cout << to_log << std::endl; \
} while (false)

and then

LOG("This is one type: " << one_type << " and here is a different type: " << diff_type);

would get expanded to

do
{
    std::cout << "This is one type: " << one_type << " and here is a different type: " << diff_type << std::endl;
} while (false);
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • I personally would yet append `std::endl` – usually one wants to have each log entry on its own line anyway and I wouldn't impose that on the user... – Aconcagua Aug 10 '22 at 16:13
  • @Aconcagua Good advice and the OP was doing that in their own code so I've added it in. – NathanOliver Aug 10 '22 at 16:28
0
template <typename ... T>
void log(T const& ... t)
{
    ( std::cout << ... << t );
    // maybe want to add a newline?
    std::cout << std::endl; // note: '\n' just adds it,
                            // std::endl flushes the stream... 
}

would allow to log (to console!) as:

log("this is an int: ", 1210, " and this is a double: ", 10.12);

Note the commas instead of the stream operator (<<)...

If you want to log to arbitrary streams you might need to add it as yet another parameter (/*...*/ void log(std::ostream& s, T const&... t)) or you might (among other solutions) initialise some singleton that you access from within the logging function:

void initialiseLogging(std::ostream& s)
{
    Logger::instance().initialize(s);
}

template <typename ... T>
void log(T const& ... t)
{
     // assumes returning a reference:
    ( Logger::instance().stream() << ... << t );
}

Above variant assumes initialisation being required and logging without yielding undefined behaviour (needs to be documented!). If you want to be able to log without you need to change:

{
    // now assuming a pointer being returned!
    auto s = Logger::instance().stream();

    // variant 1: log to nowhere if not initialised:
    if(s)
    {
        ( *s << ... << t );
    }

    // variant 2: default logging to console
    s = s ? s : &std::cout;
    ( *s << ... << t );
}  
Aconcagua
  • 24,880
  • 4
  • 34
  • 59