I've always wondered what's the most elegant way of implementing PrintLn in C++. I have not yet come with perfect conclusion. This is my shot.
Shortcut for std::cout, std::cerr and std::clog "print line" version. So you can type
CoutLn << "Hello world!";
instead of std::cout << "Hello world!" << '\n';
CoutLn, CerrLn, ClogLn are implemented.
#include <iostream>
#include <typeinfo>
#include <mutex>
/* Template magic: determine if our type is printable. */
template<typename S, typename T, typename = void>
struct is_to_stream_writable : std::false_type {};
template<typename S, typename T>
struct is_to_stream_writable<S, T, std::void_t<decltype(std::declval<S&>()<<std::declval<T>())>> : std::true_type {};
/* Since std::cout, std::cerr etc. are all different instances of the same type,
* let's define dummy classes for detecting different instances. */
class Cout{};
class Cerr{};
class Clog{};
template<class Stream = Cout>
class PrintLn {
private:
static std::ostream* stream;
static std::mutex m;
public:
PrintLn() {
if constexpr (std::is_same<Stream, Cout>::value) stream = &std::cout;
else if constexpr (std::is_same<Stream, Cerr>::value) stream = &std::cerr;
else if constexpr (std::is_same<Stream, Clog>::value) stream = &std::clog;
}
template<class T>
PrintLn& operator<<(const T& msg) {
static_assert(is_to_stream_writable<std::ostream, T>::value, "your type is not printable");
std::lock_guard<std::mutex>(PrintLn<Stream>::m);
*stream << msg;
return *this;
}
~PrintLn() {
std::lock_guard<std::mutex>(PrintLn<Stream>::m);
*stream << '\n';
}
};
/* Declare static variables. */
template<class Stream>
typename::std::mutex PrintLn<Stream>::m;
template<class Stream>
typename::std::ostream* PrintLn<Stream>::stream;
using CoutLn = PrintLn<Cout>;
using CerrLn = PrintLn<Cerr>;
using ClogLn = PrintLn<Clog>;
/* Shorter syntax, for example: `CerrLn{} << "error";` -> `CerrLn << "error";`. */
#define CoutLn CoutLn{}
#define CerrLn CerrLn{}
#define ClogLn ClogLn{}
1 Answer 1
Have you looked at C++20 osyncstream
? It seems to have a better interface.
Multithreading
std::lock_guard<std::mutex>(PrintLn<Stream>::m);
This line is useless, because temporary objects are destroyed at the end of the full-expression. (Does this even have a temporary in C++17?) You need a named variable instead. Also make use of class template argument deduction:
std::lock_guard lock{m};
Also,
Streams
Your approach is unnecessarily restricted because only three hardcoded streams std::cout
, std::cerr
, and std::clog
are supported. And I think {}
is OK and these don't really help a lot:
#define CoutLn CoutLn{}
#define CerrLn CerrLn{}
#define ClogLn ClogLn{}
Make the function object have regular semantics instead. You may use a hash map internally to store the mutexes, as syncbuf
does.
Also SFINAE on operator<<
.
Here's the same thing implemented with osyncstream
:
template <
class CharT,
class Traits = std::char_traits<CharT>,
class Allocator = std::allocator<CharT>
> class PrintLn : public std::basic_osyncstream<CharT, Traits, Allocator> {
using Base = std::basic_osyncstream<CharT, Traits, Allocator>;
public:
using Base::Base;
PrintLn(PrintLn&&) = default;
PrintLn& operator=(PrintLn&&) = default;
~PrintLn()
{
if (this->get_wrapped()) {
*static_cast<Base*>(this) << '\n';
}
}
};
inline auto cout_ln()
{
return PrintLn{std::cout};
}
inline auto cerr_ln()
{
return PrintLn{std::cerr};
}
inline auto clog_ln()
{
return PrintLn{std::clog};
}
But anyway, why would you wanna do this when you can simply print a \n
?
-
\$\begingroup\$ I wouldn't recommend C++20 features until most major compilers (G++, Clang++, MSVC++) support them. Even then, it might not hurt to wait a couple of years so those compilers that support them get to enough platforms. \$\endgroup\$S.S. Anne– S.S. Anne2020年05月02日 17:49:14 +00:00Commented May 2, 2020 at 17:49