Stream Manipulation

The following variables, functions and classes are all defined within the namespace HIPP.

Predefined Variables

extern PStream pout
extern PStream perr

Pretty output streams for standard output and standard error. See below PStream for the detail.

extern PLogStream plog

Stream for producing pretty log records. See below PLogStream for the detail.

Formatted IO

PStream

class PStream

PStream (Pretty Stream) is a wrapper for std::ostream. It provides shortcuts which enable elegant output.

type stream_op_t

The internal type that actually performs output.

PStream(ostream &os) noexcept

Constructor. Wrap the stream os. The output is then made on it.

An internal reference is kept to os. So, make sure it does not destroyed on the output calls of PStream.

ostream &get_stream() const noexcept

Retrive the internal reference to os passed in the constructor.

stream_op_t &operator<<(ostream &(*pf)(ostream&))
stream_op_t &operator<<(std::ios &(*pf)(std::ios&))
stream_op_t &operator<<(std::ios_base &(*pf)(std::ios_base&))
template<typename T>
stream_op_t &operator<<(T &&x)

Output a specifier pf (e.g., std::endl, std::setw) or an object x to the stream. The first output should be made with operate<<, and the subsequent ones should be made with the comma operator (see below examples).

Possible types of x are:

Standard container

array, deque, forward_list, list, map, multimap, unordered_map, unordered_multimap, set, multiset, unordered_set, unordered_multiset, vector.

Pair of input iterator

e.g., pout(b, e). See below examples.

Standard composite type

pair, tuple

Object with operator<<

The operator<< is used to generate printing.

Any other object

Generate a platform-dependent printing.

If the objects satisfying the above constraints make up of a nested object, it can also be put into the stream, e.g. vector<vector<double> > and tuple<string, int, vector<double> >.

template<typename It>
stream_op_t::it_pair_t<It> operator()(It b, It e)

Wrap a pair of input iterators to an object which can be passed to PStream for output.

Example:

Here we output some objects to pout, which is a wrapper of std::cout but provides more elegant printing interface for any object.

Scalar or array can be printed like:

int a[3] = {1,2,3};

pout << "a[0]=", a[0], ", a[1]=", a[1], ", a[2]=", a[2], '\n',
        "a = ", pout(a, a+3), endl;

Output:

a[0]=1, a[1]=2, a[2]=3
a = 1,2,3

STL containers and utilities can directly streamed. For example, std::vector, std::unordered_map and std::tuple can be printed by:

vector<double> b = {1.1, 2.1, 3.1};
std::unordered_map<string, double> c = {{"bar", 10.0}, {"foo", 20.0}, {"baz", 30.0}};
std::tuple<string, int, vector<double>> d = {"a string", 1, {2.0,3.0, 4.0}};

pout << "b = (", b, ")\n",
    "c = {", c, "}\n",
    "d = ", d, endl;

Output:

b = (1.1,2.1,3.1)
c = {foo:20,baz:30,bar:10}
d = a string:1:2,3,4

Object with overloaded operator<< can be streamed. Otherwise, a compiler-dependent printing is made. For example, the following class A has no operator<< defined, but class B does:

struct A {};
struct B {
    friend ostream & operator<<(ostream &os, const B &b) {
        return os << "B object";
    }
};
pout << "A() = ", A(), '\n',
        "B() = ", B(), endl;

Output:

A() = <1A instance at 0x7ffe76760f08>
B() = B object

PrtArray

template<typename InputIterator>
class PrtArray

PrtArray handles the formatted IO for array-like objects (e.g., raw array, std::vector, etc.).

PrtArray can be copy/move-constructed, copy/move-assigned, where the format controllers and the iterators are copied/moved. The destructor and move operations are noexcept.

Several internal format specifiers/controllers are used to control the IO of the array:

Format specifier

Default value

Effect

indent

ind=0 (no indent)

the indent at each line-start.

width

w=-1 (no padding)

the width of each printed item.

ncol

nc=-1 (no line-break)

break the array into several rows, each has ncol elements.

coloffset

coff=0

offset of the first element

sep

sep=","

separation to print between elements

endline

el="\n"

what to print at the end of each row

endlast

elast=""

what to print after the last elements

Given an array a, the final output is (where sep is determined by width):

ind pad a[0]      sep pad a[1]       ...   sep pad a[ncol-1]   sep el
ind pad a[ncol]   sep pad a[ncol+1]  ...   sep pad a[2*ncol-1] sep el
... pad item[n-2] sep pad a[n-1]     elast

Examples:

vector<double> arr1 = {1,2,3,4,5,6,7,8};
PrtArray p_arr1(arr1);
cout << p_arr1 << endl;

The output is

1,2,3,4,5,6,7,8

Now we set the number of columns ncol to 3:

p_arr1.ncol(3);
cout << p_arr1 << endl;

The output is

1,2,3,
4,5,6,
7,8

Now we set the field width width to 4:

p_arr1.width(4);
cout << p_arr1 << endl;

The output is

# width is 4 for elements
    1,   2,   3,
    4,   5,   6,
    7,   8

Now we print two arrays together using the coloffset for the second array:

cout << p_arr1;
p_arr1.coloffset( arr1.size()%3 );
cout << p_arr1 << endl;

The output is

# width is 4 for elements; two arrays are joined
    1,   2,   3,
    4,   5,   6,
    7,   8,   1,
    2,   3,   4,
    5,   6,   7,
    8
typedef InputIterator iter_t

The input iterator type which is used as the source of printing.

template<typename Container>
explicit PrtArray(const Container &array)
PrtArray(iter_t b, iter_t e)

The constructors.

PrtArray is constructed by a pair of input interators b and e. The range of elements refered by them are then printed on the call of the function operator<<() or the method prt(). Two overloads are:

  1. using an array-like object array - std::begin() and std::end() are used to extract the range iterators.

  2. using directly the pair of iterators.

ostream &prt(ostream &os = cout) const
friend ostream &operator<<(ostream &os, const PrtArray &arr)

Print the content specified by the range iterators into the stream os.

std::pair<iter_t, iter_t> get_iter() const
std::ptrdiff_t indent() const noexcept
const string &endline() const noexcept
const string &sep() const noexcept
const string &endlast() const noexcept
std::ptrdiff_t ncol() const noexcept
std::ptrdiff_t coloffset() const noexcept
std::ptrdiff_t width() const noexcept

Retrive the current range iterators or the format specifiers.

template<typename Container>
PrtArray &set_array(const Container &array)
PrtArray &set_array(iter_t b, iter_t e)
PrtArray &indent(std::ptrdiff_t value)
PrtArray &endline(const string &value)
PrtArray &sep(const string &value)
PrtArray &endlast(const string &value)
PrtArray &ncol(std::ptrdiff_t value)
PrtArray &coloffset(std::ptrdiff_t value)
PrtArray &width(std::ptrdiff_t value)
PrtArray &reset_fmt()

Set the range iterators or the format specifiers.

set_array() can be called with either a single array (where std::begin() and std::end() are used to extract the range iterators) or a pair of iterators.

reset_fmt() resets all the format specifiers to their default values.

Logger

class PLogStream : protected PStream

PLogStream - pretty stream for producing log.

PLogStream is like PStream, has overloaded << operator which can then be chained using the comma operator.

PLogStream has extra methods to produce log records. Its features include

  • Log entries are nested with indents hinting the stack height, i.e., PLogStream allows entering/leaving scopes, and produces extra indents in the more inner scope.

  • The type of information is printed can be controlled, i.e., log entries can be filtered according to its priority level.

Memory management methods:

Method

Detail

default constructor

Not available.

copy and move constructors

Defined; deep-copy.

operator=(&&)
and operator=(const &)

Deleted.

destructor

noexcept.

typedef _hippcntl_stream_pretty_log_helper::Guard guard_t
typedef _hippcntl_stream_pretty_log_helper::StreamOperand stream_op_t
enum [anonymous] : int
enumerator LV_EMERG = 0
enumerator LV_ALERT = 1
enumerator LV_CRIT = 2
enumerator LV_ERR = 3
enumerator LV_WARNING = 4
enumerator LV_NOTICE = 5
enumerator LV_INFO = 6
enumerator LV_DEBUG = 7
enumerator LV_MIN = -1
enumerator LV_MAX = 100
enum [anonymous] : size_t
enumerator ENTRY_PREFIX_MAXLEN = 32
PLogStream(ostream &os, int level = LV_NOTICE, int level_used = LV_NOTICE, int stack_height = 0) noexcept

Constructor.

Parameters
  • os – which the log output are directed to.

  • level – the current priority level.

  • level_used – under which level the information should be printed.

  • stack_height – the depth of nesting.

PLogStream &set_level(int level = LV_NOTICE) noexcept
PLogStream &set_level_used(int level_used = LV_NOTICE) noexcept
PLogStream &set_indent(int indent = 2) noexcept
PLogStream &set_entry_prefix(const string &entry_prefix = "|- ") noexcept

Attribute setters.

  • set_level() - current priority level.

  • set_level_used() - used priority level. Log entries with level <= level_used will be printed.

  • set_indent() - number of extra spaces padded at the front of each log entry.

  • set_entry_prefix() - prefix padded at the front of each log entry. The prefix should be no longer than ENTRY_PREFIX_MAXLEN-1. Otherwise it is truncated.

int level() const noexcept
int level_used() const noexcept
int indent() const noexcept
string entry_prefix() const noexcept
int stack_height() const noexcept

Attribute getters.

  • stack_height() - current stack height. If the height is non-zero, indent*stack_height spaces and entry_prefix are printed for each entry. Otherwise nothing is padded.

Other attributes are explained in the corresponding setters.

stream_op_t operator<<(ostream &(*pf)(ostream&))
stream_op_t operator<<(std::ios &(*pf)(std::ios&))
stream_op_t operator<<(std::ios_base &(*pf)(std::ios_base&))
template<typename T>
stream_op_t operator<<(T &&x)

Output an object to this stream. The object can be an standard formatter pf, or any object x that is printable to PStream.

The returned stream_op_t object allows chaining the subsequent outputs using comma operator, e.g., plog << "the object ", x, endl;.

The contents output by << and , in a single statement is viewed as a single log entry. Paddings, as controlled by indent and entry_prefix is added.

template<typename ...Args>
void push(Args&&... args)
void pop(bool hint = false)

push() increases the stack height by 1, while pop() decreases it by 1. These two operations are used to increse the indent of log entry.

It is always recommended to use the guarded version push_g() and push_at() instead of the direct push() and pop(). (RAII style)

For push(), if args are non-empty, they are ouput into the stream, with indent*stack_height spaces padding at front. Then, stack height is increased.

For pop(), if hint is true, an entry is output, and then the stack height is decreased.

template<typename ...Args>
guard_t push_g(Args&&... args)
template<typename ...Args>
guard_t push_at(int level, Args&&... args)

The guarded versions of push()

push_g() - call push() and returns a guard.

push_at() - change the level to level, call push(), and returns a guard.

The guard is responsible for restoring the stack_height on its destruction (similar to calling pop()). The level is also restored if its was changed.

template<typename It>
stream_op_t::it_pair_t<It> operator()(It b, It e)

Objects in an iterable range defined by iterators [b, e) can be printed by, e.g., pout << "{", pout(b, e), "}", endl;

This is particularly useful for the raw array, like:

int a[N];
int *a = new int [N];
ostream &get_stream() const noexcept

Return a reference to the internal std::ostream object.

Examples:

To perform logging, just use << to put content into the stream, and chain the subsequent contents with ,:

plog << "Begin computation", endl;
int sum = 0;
for(int i=0; i<5; ++i)
    sum += i;
plog << "Sum from 0 to 4 is ", sum, endl;

The output is

Begin computation
Sum from 0 to 4 is 10

To enter a new scope, call push_g() and name the returned guard object. On the destruction of the guard object, we automatically leave the scope. The following codes show a function outer(), with a nested inner() inside it:

double inner(double x) {
    auto g = plog.push_g(emFF);
    plog << "Begin computation for x=", x, "", endl;
    double y = std::sin(x);
    plog << "Got result ", y, endl;
    return x;
}

void outer() {
    auto g = plog.push_g(emFF, "- the outer loop");
    vector<double> xs = {1., 2., 3.}, ys;
    plog << "Begin computation for ", xs.size(), " values" , endl;
    plog << "Inputs = {", xs, "}", endl;
    for(size_t i=0; i<xs.size(); ++i){
        ys.push_back(inner(xs[i]));
    }
    plog << "Got all results {", ys, "}", endl;
}

Here inner() just computes the sine of the input. emFF is a shortcut macro which gives the current source file name (“plog.cpp”) and the function name (“outer”).

A call of outer() then outputs

[file] plog.cpp, [func] outer - the outer loop
  |- Begin computation for 3 values
  |- Inputs = {1,2,3}
  [file] plog.cpp, [func] inner
    |- Begin computation for x=1
    |- Got result 0.841471
  [file] plog.cpp, [func] inner
    |- Begin computation for x=2
    |- Got result 0.909297
  [file] plog.cpp, [func] inner
    |- Begin computation for x=3
    |- Got result 0.14112
  |- Got all results {1,2,3}

To specify the priority level in the scope, call push_at() with the first argument specifying the desired level. The default level is LV_NOTICE. The following codes show how to enter the LV_INFO and LV_DEBUG levels, respectively. Those two levels have lower priority than the NOTICE level:

void log_with_filter() {
    plog << "With filter", endl;
    plog << "Log entries with low priority levels are not printed", endl;
    {
        auto _ = plog.push_at(plog.LV_INFO, "Lower-priority block");
        plog << "Printed only when the INFO level is required", endl;
        plog << "Begin execution", endl;
        /* work on a task */
        {
            auto _ = plog.push_at(plog.LV_DEBUG, "Even lower-priority block");
            plog << "Printed only when the DEBUG level is required", endl;
            plog << "Begin more detail of the task", endl;
            /* work on detail */
            plog << "End successfully", endl;
        }
        plog << "End execution", endl;
    }
    plog << "End of subroutine", endl;
}

A call of log_with_filter() outputs

With filter
Log entries with low priority levels are not printed
End of subroutine

Where the log entries with priority lower than LV_NOTICE are not printed. To allow printing the INFO entries, call set_level_used():

plog.set_level_used(plog.LV_INFO);
log_with_filter();

Then the output give all logs with priority equal or higher than the INFO level:

With filter
Log entries with low priority levels are not printed
Lower-priority block
|- Begin execution
|- End execution
End of subroutine

If the LV_DEBUG is turned on:

plog.set_level_used(plog.LV_DEBUG);
log_with_filter();

Then, more logs are printed

With filter
Log entries with low priority levels are not printed
Lower-priority block
|- Printed only when the INFO level is required
|- Begin execution
Even lower-priority block
    |- Printed only when the DEBUG level is required
    |- Begin more detail of the task
    |- End successfully
|- End execution
End of subroutine

Shortcuts for formatted IO and Factories of Strings

template<typename ...Args>
ostream &prt(ostream &os, Args&&... args)
template<typename ...Args>
ostream &prt_f(ostream &os, const char *fmt, Args&&... args)
template<typename ...Args>
ostream &prt_f(ostream &os, const string &fmt, Args&&... args)
template<typename Container>
ostream &prt_a(ostream &os, const Container &array)
template<typename InputIterator>
ostream &prt_a(ostream &os, InputIterator b, InputIterator e)

Print args into an output stream os. os is returned.

prt(): print args`. No padding is added between adjacent arguments or at end. Each argument in ``args must have overloaded operator<<.

prt_f(): use formatting string fmt to format the output. The formatting is the same as standard output functions printf().

prt_a(): prints an array of elements. The array can be specified by a single container array (std::begin and std::end are applied to determine its iterable range) or a pair of iterators b, e. Adjacent elements are separated by a comma “,”. No padding is added at the beginning or at the end of the printed list. For a more fine-tuning controlling, use PrtArray class instead.

Examples:

Use prt() and its variants to print any number or arguments into std::cout:

string str = "hello";
const char *cstr = "world!";
HIPP::prt(cout, str, ", ", cstr);
/* equivalent to call cout << str << ", " << c_str; */

const char *cstr = "world!";
HIPP::prt_f(cout, "hello, %s", cstr);
/* equivalent to call printf("hello, %s", cstr) */

vector<int> arr(5, 1);
HIPP::prt_a(cout, arr) << endl;
HIPP::prt_a(cout, arr.begin(), arr.begin()+4) << endl;
/* In the output devide, printed results would be
   1,1,1,1,1
   1,1,1,1
*/
template<typename ...Args>
string str(Args&&... args)
template<typename ...Args>
string str_f(const char *fmt, Args&&... args)
template<typename ...Args>
string str_f(const string &fmt, Args&&... args)
template<typename Container>
string str_a(const Container &array)
template<typename InputIterator>
string str_a(InputIterator b, InputIterator e)

String factories. These functions accept the same arguments as prt() and its variants do, but return a std::string instead of printing the content into a stream.

Examples:

In the following example we create 10 files whose names are constructed by the str():

for(int i=0; i<10; ++i){
    ofstream ofs(str("file.", i, ".txt") );
    /* ... write to ofs ... */
}

/* after execution we have 10 files named
   file.0.txt
   file.1.txt
   ...
*/

If the format of number in the string needs carefully control, prt_f() would be helpful. Numeric simulations with multiple snapshot dumped usually name the files this way. For example:

for(int i=0; i<10; ++i){
    double redshift = 0.01 * i;
    ofstream ofs(str_f("snapshot.%03d.z%.2f.txt", i, redshift));
    /* ... write to ofs */
}

/* after execution we have 10 files named
   snapshot.000.z0.00.txt
   snapshot.001.z0.01.txt
   ...
*/

Helpers

The following classes are defined as helpers for the main classes. Users generally do not use them directly, and should get access to them by calling the methods of the main classes.

class _hippcntl_stream_pretty_log_helper::Guard

Guard of the push operation of PLogStream.

On the calling of a guarded push, the PLogStream releases a guard object. On the destruction of the guard object, the side effect of push operation is restored.

Memory management methods:

Method

Detail

default constructor

Not avaiable.

copy constructor
and operator=(&&)

Deleted.

move constructor
and operator=(const &)

Available; noexcept.

destructor

noexcept.

Guard(PLogStream &pls, bool hint_pop = false)

Constructor. Get a guard to the current scope for a stream pls.

Parameters

hint_pop – see the attribute setter hint_pop_on() and hint_pop_off().

void hint_pop_on() noexcept
void hint_pop_off() noexcept

Switch on/off the hint on pop.

If on, an extra entry is output to the PLogStream at the destruction of the guard, similar to calling PLogStream::pop() with true.

bool hint_pop() const noexcept

Attribute getter - whether the hint on pop is turned on.

void set_level(int level, bool scoped = true) noexcept

Change the level of the PLogStream.

Parameters

scoped – if true, the guard object is responsible for restoring the old level. Otherwise the level is really changed - the guard does not restore it.

class StreamOperand : protected PStream::stream_op_t

The stream operand of PLogStream. See the description of PLogStream::operator<<() for the detail usage of the stream operand.

Memory management methods:

Method

Detail

default constructor

Not avaiable.

copy constructor
and operator=(&&)

Deleted. noexcept. The copied one refers to the same stream object.

move constructor
and operator=(const &)

Available; noexcept.

destructor

noexcept.

typedef PStream::stream_op_t parent_t
type parent_t::it_pair_t
StreamOperand(ostream &os, bool enabled) noexcept
StreamOperand(const parent_t &op, bool enabled) noexcept

Constructor.

Parameters
  • os – the operand refers to that stream and the comma operator outputs conntent into it.

  • op – the operand refers to the same stream as op.

  • eanbled – if true, output will show. Otherwise no output is shown.

StreamOperand &operator,(ostream &(*pf)(ostream&))
StreamOperand &operator,(std::ios &(*pf)(std::ios&))
StreamOperand &operator,(std::ios_base &(*pf)(std::ios_base&))
template<typename T>
StreamOperand &operator,(T &&x)

The comma operator allows chaining outputs.

ostream &get_stream() const noexcept

Return a reference to the internal std::ostream object.