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 c++20, 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 c++17) 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(...);
.