Hippo¶
Hierarchical Information as Pretty-Printed Objects¶
Hippo is a header-only library for C++17 that provides facilities for pretty-printing user-defined types.
For more information on a specific feature, see the pages below:
Introduction¶
Using Hippo¶
Hippo is a header-only library, so install the headers however you’d like.
To begin using Hippo, include the following:
#include "hippo/hippo.h"
Printing a value¶
Printing values is performed by the hippo::print()
or hippo::print_to()
functions.
Both functions take a value and a hippo::configuration
and produce a pretty-printed output.
A simple example of printing a vector:
#include "hippo/hippo.h"
#include "hippo/std/vector.h"
#include <iostream>
int main() {
std::vector<int> v {0, 1, 2};
hippo::print_to(std::cout, v, hippo::configuration());
}
This example will print:
std::vector [0, 1, 2]
Interface¶
-
struct
configuration
¶ Global configuration values applied to all printers.
-
template<typename
T
>
std::vector<std::string>hippo
::
print
(const T &t, const hippo::configuration &config)¶ Print any printable value
t
with configurationconfig
-
template<typename
T
>
std::ostream &hippo
::
print_to
(std::ostream &os, const T &t, const hippo::configuration &config)¶ Print any printable value
t
with configurationconfig
to the specifiedstd::ostream
Printing user-defined types via reflection¶
A key feature of Hippo is the ease of printing user-defined types.
Classes¶
Hippo provides utilities for printing user-defined types. Consider the following types:
struct Foo {
int a;
float b;
};
struct Bar {
std::vector<Foo> foos;
};
To print these types, we reflect them using HIPPO_CLASS_BEGIN
, HIPPO_MEMBER
, and HIPPO_CLASS_END
:
#include "hippo/hippo.h" // reflection macros
#include "hippo/std/vector.h" // std::vector printer
HIPPO_CLASS_BEGIN(Foo)
HIPPO_MEMBER(a)
HIPPO_MEMBER(b)
HIPPO_CLASS_END()
HIPPO_CLASS_BEGIN(Bar)
HIPPO_MEMBER(foos)
HIPPO_CLASS_END()
The printers for int
, float
, and std::vector
are all provided by Hippo.
Once we’ve provided the printer for Foo
, we are able to use it to print Bar
.
A printed instance of Bar
might look something like this:
Bar {
foos: std::vector [
Foo { a: 1, b: 0.5 },
Foo { a: 2, b: -3.1 }
]
}
Enums¶
Like classes, enums can be reflected wish HIPPO_ENUM_BEGIN
, HIPPO_ENUM_VALUE
, and HIPPO_ENUM_END
:
enum Foo {
Bar,
Baz
};
HIPPO_ENUM_BEGIN(Foo)
HIPPO_ENUM_VALUE(Bar)
HIPPO_ENUM_VALUE(Baz)
HIPPO_ENUM_END()
Base classes¶
Hippo can also reflect base classes with HIPPO_BASE
:
struct Foo : Bar {
/* members */
};
HIPPO_CLASS_BEGIN(Foo)
HIPPO_BASE(Bar)
/* members */
HIPPO_CLASS_END()
Custom member access expressions¶
In some cases, it’s useful to use another expression to access a member.
This is accomplished by using the HIPPO_MEMBER_EXPR
macro, which allows a custom expression to be provided, operating on the input object
:
class Foo {
int bar;
public:
Foo(int bar) : bar(bar) {}
int get_bar() const { return bar; }
};
HIPPO_CLASS_BEGIN(Foo)
HIPPO_MEMBER_EXPR(bar, object.get_bar())
HIPPO_CLASS_END()
Interface¶
Class reflection¶
-
HIPPO_CLASS_BEGIN
(Type)¶ Begin the definition of a printer specialization for a class
Type
-
HIPPO_CLASS_END
()¶ End the definition of a printer specialization for a class.
-
HIPPO_BASE
(Type)¶ Register
Type
as a base class in a class printer specialization.
-
HIPPO_MEMBER
(Name)¶ Register
Name
as a member in a class printer specialization.
-
HIPPO_MEMBER_EXPR
(Name, Expression)¶ Register
Name
as a member, printed asExpression
, in a class printer specialization.
Formatting¶
Some printable types support formatting.
Formatting is applied with the hippo::formatter
adapter, which itself is a printable type that applies a format to its contents.
Formatting numbers¶
The following example shows how numbers can be formatted for a user-defined type:
struct Foo {
int bar;
float baz;
};
static hippo::integer_format hex() {
hippo::integer_format fmt;
fmt.base = hippo::integer_format::base_type::hex;
return fmt;
}
static hippo::float_format scientific() {
hippo::float_format fmt;
fmt.format = hippo::float_format::notation_type::scientific;
return fmt;
};
HIPPO_CLASS_BEGIN(Foo)
HIPPO_CLASS_MEMBER_EXPR(Foo, hippo::formatter(object.bar, hex()))
HIPPO_CLASS_MEMBER_EXPR(Foo, hippo::formatter(object.baz, scientific()))
HIPPO_CLASS_END()
Using formatting to print polymorphic types¶
Polymorphic types can be printed by use of hippo::derived_type_printer
:
#include "hippo/hippo.h"
#include "hippo/std/memory.h"
#include <iostream>
struct Foo {
virtual ~Foo() = default;
};
struct Bar : Foo {};
struct Baz : Foo {};
HIPPO_CLASS_BEGIN(Foo)
HIPPO_CLASS_END()
HIPPO_CLASS_BEGIN(Bar)
HIPPO_BASE(Foo)
HIPPO_CLASS_END()
HIPPO_CLASS_BEGIN(Baz)
HIPPO_BASE(Foo)
HIPPO_CLASS_END()
int main() {
std::shared_ptr<Foo> foo = std::make_shared<Foo>();
std::shared_ptr<Foo> bar = std::make_shared<Bar>();
std::shared_ptr<Foo> baz = std::make_shared<Baz>();
hippo::dynamic_type_format<Foo> dyn_fmt;
dyn_fmt.printers.push_back(std::make_shared<hippo::derived_type_printer<Foo, Bar>>());
dyn_fmt.printers.push_back(std::make_shared<hippo::derived_type_printer<Foo, Baz>>());
hippo::pointer_format<Foo> fmt = std::move(dyn_fmt);
hippo::print_to(std::cout, hippo::formatter(foo, fmt), hippo::configuration());
hippo::print_to(std::cout, hippo::formatter(bar, fmt), hippo::configuration());
hippo::print_to(std::cout, hippo::formatter(baz, fmt), hippo::configuration());
}
Once Bar
and Baz
are registered with the pointer format, the printer is able to use RTTI to determine which printer to use.
The following is printed:
std::shared_ptr containing [ Foo { } ]
std::shared_ptr containing [ Bar { Base Foo { } } ]
std::shared_ptr containing [ Baz { Base Foo { } } ]
Interface¶
-
template<typename
T
>
structformatter
¶ A printable type that applies formats to other printable types.
Public Types
-
template<>
usingvalue_type
= std::remove_const_t<T>¶ The type to format.
-
template<>
usingprinter_type
= hippo::printer<value_type>¶ The printer for
T
-
template<>
usingformat_type
= typename printer_type::format_type¶ The format configuration for
T
Public Functions
-
formatter
(const value_type &value, const format_type &format)¶ Construct a
formatter
that printsvalue
with the format described byformat
. The constructedformatter
does not ownvalue
orformat
, so both must remain in scope for the lifetime of theformatter
.
-
template<>
-
template<typename
T
>
structformatter
<T *>¶ Specialization of
formatter
for pointer types.Public Types
-
template<>
usingvalue_type
= std::remove_const_t<std::decay_t<T>>¶ The type to format.
-
template<>
usingprinter_type
= hippo::printer<value_type *>¶ The printer for
T
-
template<>
usingformat_type
= typename printer_type::format_type¶ The format configuration for
T
Public Functions
-
formatter
(const value_type *value, const format_type &format)¶ Construct a
formatter
that printsvalue
with the format described byformat
. The constructedformatter
does not ownvalue
orformat
, so both must remain in scope for the lifetime of theformatter
.
-
template<>
-
struct
no_format
¶ Format for non-formattable types.
Number format configurations¶
-
struct
integer_format
¶ Format for integer values.
Public Types
-
struct
float_format
¶ Format for floating-point values.
Public Types
Public Members
-
notation_type
notation
¶ Notation format, defaults to
standard
-
std::optional<std::size_t>
precision
¶ Precision for
std::setprecision
-
notation_type
Pointer configurations¶
-
using
hippo
::
pointer_format
= std::variant<standard_pointer_format<T>, address_format, dynamic_type_format<T>>¶ Format for printing a pointer.
-
template<typename
T
>
structstandard_pointer_format
¶ Format option for non-polymorphic pointers. A non-null pointer is dereferenced and printed.
Public Members
-
format_type
format
¶ The format used for printing.
-
format_type
-
struct
address_format
¶ Format option for printing pointers as addresses (rather than printing the dereferenced pointer)
-
template<typename
T
>
structdynamic_type_format
¶ Format option for printing polymorphic types. A non-null pointer is checked against the registered types, dereferenced, and printed.
Public Types
Public Members
-
std::vector<std::shared_ptr<base_type_printer<T>>>
printers
¶ Printers for derived types, in preference order.
Printers are called one by one and returns the first successful output.
-
base_format_type
base_format
¶ If none of the derived printers are successful, the base class is printed with this format.
-
std::vector<std::shared_ptr<base_type_printer<T>>>
-
template<typename
Base
>
structbase_type_printer
¶ Abstract base for printers of polymorphic pointers.
Subclassed by hippo::derived_type_printer< Base, Derived >
Public Functions
-
virtual std::optional<hippo::object>
print
(const Base *b, std::uint64_t current_indent, const hippo::configuration &config) = 0¶ Prints
b
if possible, otherwise the return value is empty.
-
virtual std::optional<hippo::object>
-
template<typename
Base
, typenameDerived
>
structderived_type_printer
: public hippo::base_type_printer<Base>¶ Printer for a polymorphic type from a base class pointer.
Public Types
-
template<>
usingformat_type
= typename printer_type::format_type¶ Format type of
Derived
Public Functions
-
derived_type_printer
()¶ Construct a printer using the default format.
-
derived_type_printer
(const format_type &format)¶ Construct a printer using the specified format
format
-
std::optional<hippo::object>
print
(const Base *b, std::uint64_t current_indent, const hippo::configuration &config)¶ Prints
b
if it is aDerived
, otherwise returns nothing.
-
template<>
Out-of-the-box type support¶
As discussed in Printing user-defined types via reflection, struct
, class
, enum
, and enum class
are all supported via macros.
In addition to user defined types, most types provided by the language are automatically supported. All builtin types are supported, as well as many from the standard library.
Supported standard library types¶
Strings¶
In addition to const char *
, Hippo supports std::string
via "hippo/std/string.h"
.
Containers¶
Support for all containers is available:
std::array
via"hippo/std/array.h"
std::vector
via"hippo/std/vector.h"
std::list
via"hippo/std/list.h"
std::forward_list
via"hippo/std/forward_list.h"
std::deque
via"hippo/std/deque.h"
std::set
andstd::multiset
via"hippo/std/set.h"
std::unordered_set
andstd::unordered_multiset
via"hippo/std/unordered_set.h"
std::map
andstd::multimap
via"hippo/std/map.h"
std::unordered_map
andstd::unordered_multimap
via"hippo/std/unordered_map.h"
All containers can be formatted with the format configuration of the inner type(s). Map types can be formatted with:
Tuples¶
Both std::pair
and std::tuple
are supported, by "hippo/std/utility.h"
and "hippo/std/tuple.h"
, respectively.
They can be formatted with:
Smart pointers¶
In addition to plain pointers, std::unique_ptr
, std::shared_ptr
, and std::weak_ptr
are supported via "hippo/std/memory.h"
.
These types are all formattable by hippo::pointer_format
.
Sum types¶
std::optional
is supported via "hippo/std/optional.h"
and is formattable with the inner type’s format configuration.
std::variant
is supported via "hippo/std/variant.h"
and is formattable with:
Chrono¶
std::chrono::duration
is supported via "hippo/std/chrono.h"
and is formattable with the inner type’s format configuration.
Complex¶
std::complex
is supported via "hippo/std/complex.h"
and is formattable with the inner type’s format configuration.
Atomic¶
std::atomic
is supported via "hippo/std/atomic.h"
and is formattable with the inner type’s format configuration.
Bitset¶
std::bitset
is supported via "hippo/std/bitset.h"
and is not formattable.
Advanced usage¶
Sometimes it is necessary to access the internal workings to create a more complicated printer.
Representing of lines of text¶
Before we get into printing types, we must understand how outputs are represented.
In Hippo, a line of text is represented by hippo::line
, which tracks the indentation level of the line separately from the contents.
-
struct
line
¶ Describes a printed line of text.
When an object is printed, the generated lines of text are then collected into a hippo::object
.
Any lines that are shorter than hippo::configuration::width
are condensed into a single line if possible.
Multiple hippo::object
may be condensed as well, but only if all of the objects are a single line.
-
using
hippo
::
object
= std::variant<hippo::line, std::list<hippo::line>>¶ Describes the printed output of any object, either as a single or multiple lines.
-
inline hippo::object
hippo
::
condense
(const std::list<hippo::line> &lines, const hippo::configuration &config) Condense a collection of
line
s into a singleobject
. Multiple lines will be condensed into one if the indented result is less than the configured output width.
-
inline hippo::object
hippo
::
condense
(const std::list<hippo::object> &objects, const hippo::configuration &config) Condense a collection of
object
s into a singleobject
. If any of the inputobject
s are multiline, the output is not condensed, otherwise the lines will condensed if the indented result is less than the configured output width.
Defining a printer¶
Printers are added for a type by specializing the hippo::printer
struct.
This class is declared as follows:
-
template<typename
T
, typenameU
= T>
structprinter
¶ The core pretty-printer type.
T
is the type to be printed.U
is provided for optionally making SFINAE possible.
Specializations must fulfill the following interface:
template<> hippo::printer<Foo> {
using format_type = /* any default-constructible and copy-constructible type */
static ::hippo::object print(const Foo &f,
std::uint64_t current_indent,
const ::hippo::configuration &config,
const format_type &format = format_type());
}
Convenient utilities¶
The following operations are so common when creating printers that Hippo provides them.
Manipulating lines¶
Formatting values¶
-
template<typename
T
>
std::enable_if_t<std::is_floating_point_v<T>, std::string>hippo
::
apply_format
(T value, const float_format &fmt) Apply format
fmt
to floating-pointvalue
-
template<typename
T
>
std::enable_if_t<std::is_integral_v<T>, std::string>hippo
::
apply_format
(T value, const integer_format &fmt) Apply format
fmt
to integervalue
-
template<typename
T
>
hippo::objecthippo
::
apply_format
(const T *value, std::uint64_t current_indent, const hippo::configuration &config, const pointer_format<T> &fmt) Apply format
fmt
to pointervalue
using thecurrent_indent
indentation level and configurationconfig
.