4
\$\begingroup\$

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{}
\$\endgroup\$
3
\$\begingroup\$

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?

\$\endgroup\$
1
  • \$\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
    May 2 '20 at 17:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.