0

I try to make some thread safe std::cout and the best solution for me will look like the following:

void print(std::ostream &out) 
{
    pthread_mutex_lock(&m_mutex);
    std::cout << out;
    pthread_mutex_unlock(&m_mutex);
}

I want to use is like this:

print("hello" << std::endl);

but unfortunately I get a compiler error:

test.cpp:38: error: no match for ‘operator<<’ (operand types are ‘std::ostream’ {aka ‘std::basic_ostream<char>’} and ‘std::ostream’ {aka ‘std::basic_ostream<char>’})
test.cpp: In function ‘void print(std::ostream&)’:
test.cpp:38:15: error: no match for ‘operator<<’ (operand types are ‘std::ostream’ {aka ‘std::basic_ostream<char>’} and ‘std::ostream’ {aka ‘std::basic_ostream<char>’})
   

as for me this message is absolutely unreadable.

what I do wrong here?

folibis
  • 12,048
  • 6
  • 54
  • 97
  • You tried to print an value of `std::ostream`, but the compiler doesn't know how to print that. – MikeCAT Mar 31 '22 at 08:59
  • 6
    What is the expression `"hello" << std::endl` supposed to do? Your whole approach looks weird – Jabberwocky Mar 31 '22 at 09:02
  • 4
    And instead of using pthread libraries, use std::unique_lock with a std::mutex. That way you use the standard library instead of an external library – Pepijn Kramer Mar 31 '22 at 09:04
  • 3
    Does this answer your question? [How to easily make std::cout thread-safe?](https://stackoverflow.com/questions/14718124/how-to-easily-make-stdcout-thread-safe) – Piotr Siupa Mar 31 '22 at 09:08
  • unfortunately I can only use c++11, our compiler is gcc 4.8.3. the same for pthread – folibis Mar 31 '22 at 09:10
  • `std::thread` is C++11 – 463035818_is_not_an_ai Mar 31 '22 at 09:11
  • *"this message is absolutely unreadable"* -- I can see "unreadable", but "absolutely unreadable"? The first part of the error message is `no match for ‘operator<<’` -- you should be able to read that much and realize that the problem involves how you use `<<` in your code (more specifically, on line 38, based on the prefix to the error message). *My point is that you should not give up on reading something when there are small bits and pieces you can process. Extract what you can, and expect to learn how to extract a tiny bit more when you resolve this error.* – JaMiT Mar 31 '22 at 09:15
  • 2
    If you're thinking that `std::cout << "hello" << std::endl` means `std::cout << ("hello" << std::endl)` - that is, `"hello" << std::endl` is sent to `std::cout` - then no, it means `(std::cout << "hello") << std::endl`. `std::cout << "hello"` returns a reference to `std::cout`. – molbdnilo Mar 31 '22 at 09:15
  • Don't use mutexes here. Use a thread-local buffer as stated by @463035818_is_not_a_number. – gexicide Mar 31 '22 at 09:19
  • `"hello" << std::endl` is evaluated before the function is called. So you are trying to do all the printing before you have locked the mutex. Then you try to `std::cout << out;` where `out` is a `std::ostream&`. – super Mar 31 '22 at 09:19

4 Answers4

2

There are several issues here.

First, std::cout << out; is wrong because there is no matching overload for operator<< for those operands (out is a std::ostream& here). This is basically what the so called "unreadable" error message was saying.

The same applies with "hello" << std::endl for the same reason.

Moreover, std::endl is a function and more that than, a templated function. If you ever wanted to pass it as an argument, you will have to specify what overload you need, in this case, std::endl<char, std::char_traits<char>>.

You can simplify the notation the following way:

auto endl = std::endl<char, std::char_traits<char>>;

This way, you can pass the previoulsy defined endl function instead.


To solve your issue, I think a good solution would be to separate the process in two steps:

  • Accumulate your data into a stream (I will use std::stringstream here)
  • Print the accumulated data.

For this purpose, you could hide all the machinery inside a helper class, let's call it Printer.

To make it as flexible as you wanted it to be, we can make use of variadic templates.
The syntax would then be changed from "hello" << value << ... to "hello", value, ....

To sum it up, we can have the definition of the Printer class as:

class Printer final
{
    private:
        std::stringstream s;

        template <typename T>
        void accumulate(T && t)
        {
            s << std::forward<T>(t);
        }

        template <typename T, typename ... Ts>
        void accumulate(T && t, Ts && ... ts)
        {
            s << std::forward<T>(t);
            accumulate(std::forward<Ts>(ts)...);
        }

    public:
        template <typename ... Ts>
        void print(Ts && ... ts)
        {
            //lock

            accumulate(std::forward<Ts>(ts)...);
            std::cout << s.view(); // Use s.str() instead if before C++20

            s.str(std::string());
            s.clear();

            //unlock
        }
};

Note: If you are before , you may replace s.view(); with s.str();.

Then you can use it as follows:

int main()
{
    auto endl = std::endl<char, std::char_traits<char>>;

    std::string val("Bye !");

    Printer p;
    p.print("Hello", " !", '\n', val, endl);

    return 0;
}

Output:

Hello !
Bye !

Live example here


Note: It would be safer to use std::scoped_lock (or std::lock_guard if before ) instead of traditional lock/unlock mechanism since it makes use of RAII to ensure the mutex is released when going out of the scope (in the case an exception is thrown before the release for example).

Note 2: If you don't want to bother with a Printer instance, you can declare everything inside as static so that you could directly use Printer::print(...);.

Fareanor
  • 5,900
  • 2
  • 11
  • 37
  • "*Note: If you are before c++20, you may replace s.view(); with s.str();.*" So why not just use [`std::osyncstream`](https://en.cppreference.com/w/cpp/io/basic_osyncstream) in C++20? – 康桓瑋 Mar 31 '22 at 11:34
  • @康桓瑋 Indeed, it would be better :) But OP mentioned only C++11 was available in his/her case, I added this comment only for completeness (and efficiency purposes). – Fareanor Mar 31 '22 at 11:38
0

If you want to use the function like

print("hello" );

and within the function you want to output

std::cout << "hello" << std::endl;

then you need to use the string literal "hello" as the function argument.

For example

std::ostream & print( const std::string &s, std::ostream &out = std::cout ) 
{
    pthread_mutex_lock(&m_mutex);
    out << s << std::endl;
    pthread_mutex_unlock(&m_mutex);

    return out;
}

//...

print("hello" );

Or the parameter can be declared as having the type std::string_view. For example

std::ostream & print( std::string_view s, std::ostream &out = std::cout ) 
{
    pthread_mutex_lock(&m_mutex);
    out << s << std::endl;
    pthread_mutex_unlock(&m_mutex);

    return out;
}
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
0

Ok, a solution that suits me perfectly is using a buffered output as suggested above.

std::stringstream stream;
stream << "value " << some_string << " is " << some_value << std::endl;
std::cout << stream.str();

I've tested this construction with 10 threads that print outs to console quite often.

folibis
  • 12,048
  • 6
  • 54
  • 97
0

You can do it with a maccro.

#define PRINT(out, message) do { scoped_lock(); out << message << std::endl; } while(false)

Then, when replaced in your code:

PRINT("hello !");
// actually is equivalent to
{
  scoped_lock();
  out << "hello !" << std::endl;
}

PRINT("a = " << a << ", b = " << b << std::endl
   << "total = " << compute_total_value());
// actually is equavalent to
{
  scoped_lock();
  out << "a = " << a << ", b = " << b << std::endl
      << "total = " << compute_total_value() << std::endl;
}

for(int i = 0; i < 10; ++i)
  PRINT("i = " << i);
// actually is equavalent to
for(int i = 0; i < 10; ++i)
{
  scoped_lock();
  out << "i = " << i << std::endl;
}

Note: if you don't want to have the automatic endl added after your message, you can replace it by std::flush.

Note: in this kind of code, you need to used a scoped lock instead of lock/unlock instructions, in order to unlock even if an exception was thrown by the stream usage or by the functions called in message.

Note: do {} while(false) is used in the maccro, because it's the only scope that can be considered as a unique instruction by the compiler. It avoid insidious bugs when used in a loop without braces.

Caduchon
  • 4,574
  • 4
  • 26
  • 67