CPP / C++ Notes - C++ Libraries Review

Table of Contents

1 C++ Libraries Review

1.1 GSL - Guideline Support Library (C++20)

1.1.1 Overview

The GSL - Guideline Support Library (not confused with GNU Scientific Library) contains implementations of some types and functions proposed by the C++ Core Guidelines for helping to enhance the code quality, reduce mistakes and make the intent clear. Note: some types of the C++ core guidelines such as std::span will be included in the C++20.

C++ Core Guidelines - Created by ISO C++ Comitee

Implementations of the GSL Library

Parts of GSL

  • Views
    • span<T>
    • string_span<T>
    • (cw) zstring
  • Owners/Containers
    • owner<T>
    • unique_ptr<T> (for old compiler that does not support C++11)
    • shared_ptr<T> (old compilers)
    • dyn_array<T>
    • stack_array<T>
  • Utilities
    • not_null<T>
    • finally()
  • Contract Support (Macros)
    • Expect() => For pre-condition validation
    • Ensure() => For post-condition validation
  • Type casting conversions
    • narrow()
    • narrow_cast()
  • Concepts
    • String
    • Number
    • Sortable
    • Pointer

Papers

1.1.2 Type: gsl::owner

The type gsl::owner is proposed in the section I11 of the core guidelines:

  • "I.11: Never transfer ownership by a raw pointer (T*) or reference (T&)"

Definition:

template <class T, class = std::enable_if_t<std::is_pointer<T>::value>>
using owner = T;

The type owner<T> is just an alias for a pointer type for annotating raw owning pointer APIs that cannot be modified. This type allows distinguishing raw owning pointers from non-owning pointers, making the intent of the code clear.

See:

Before:

IBaseClass*  factoryFunction(int key)
{
   if(key == VALUE1)
   { 
     return new Implementation1(arg1, arg2, ...);
   }
   if(key == VALUE1)
   { 
     return new Implementation2;
   }
   //  ... ... ... . 
   // Invalid input 
  return nullptr; 
} 

After:

  • The annotation gsl::owner<T> makes clear that pointer returned by factoryFunction owns memory and that it must release it later by calling the delete operator.
#include <gls/pointer>

gsl::owner<IBaseClass*>  
factoryFunction(int key)
{
   if(key == VALUE1)
   { 
     return new Implementation1(arg1, arg2, ...);
   }
   if(key == VALUE1)
   { 
     return new Implementation2;
   }
   //  ... ... ... . 
   // Invalid input 
  return nullptr; 
} 

//  --------- Usage: ------------------// 

int main()
{ 
    std::cout << " Choose the implementation: ";
    int key; 
    std::cin >> key; 

   gsl::owner<IBaseClass*> object = factoryFunction(key); 

   object->InvokeVirtualMemberFunction();
   // ... ... ... ..

  delete object;
 }

1.1.3 Type: gsl::finally

The gsl::finally can register a callable object (lambda, functor or function pointer) which is called when it goes out of scope. This function is useful for exception-safe resource disposal or cleanup.

{
   int* arr = new int [10]; 

   // RAII => Calls lambda when this object is out-of-scope 
   // and its destructor is called. 
   auto scope_guard = gsl::finally( [=]
                   { 
                      delete [] arr;
                      std::puts("Resource cleaned OK.");
                   });

   for(size_t i = 0; i < 10; i++) { /** set arrary */ }   
} // scope_guard called here. 

1.1.4 Functions: narrow_cast and narrow

The functions narrow and narrow_cast are used in several sections of the core guidelines, namely:

  • ES.46: Avoid lossy (narrowing, truncating) arithmetic conversions
  • ES.49: If you must use a cast, use a named cast
  • P.4: Ideally, a program should be statically type safe

Function: narrow_cast()

  • The function narrow_cast is equivalent to a static_cast operator, but it makes the clear and evident that a narrow cast is being performed, a casting from type to another with less bits, therefore with possible loss of information, loss of precision or overflow/undeflow error.

Usage:

#include <gls/gls> // Include all heders

double xx = 105e4;

// Same as static_cast, but the intent is more clear.  Makes easier to
// the reader spot that a loss of information or precision may happen.
// 
// Note: If an underflow/overflow happens in this case, it is
// undefined behavihor. UB
int nx = gsl::narrow_cast<int>(xx);

Function: narrow()

  • The function narrow<T>() is similar to the narrow_cast(), but it calls std::terminate or throws an exception if there is a loss of information in the narrow cast.
  • The code in the next line throws a gsl::narrowing_error exception that abnormaly terminates the program because the value 10.535e10 when casted to int, results in an overflow, there is not enough bits to store the entire integer part of this number. This runtime check makes easier to catch bugs like this.
#include <gls/gls> // Include all heders

double xx = 10.535e10;
nx = gsl::narrow<int>(xx);

1.1.5 Type: gsl::span (C++20)

The GSL class gsl::span<T> (former gsl::array_view) is a non-owning representation or a view for a contiguous allocated sequence of objects which can be a std::vector; std::array<T, size_t>; zero-terminated C-string, a buffer represented by the tuple (pointer, size) or (C-array, size). The gsl::span<T> view can also be used for refactoring C-style functions that takes the tuple parameters (pointer, size), representing a buffer, and making the function safe against memory corruption, out-of-bound read/write and buffer overflows, which can introduce security vulnerabilities.

C++20 std::span documentation (not gsl::span)

Motivating Paper:

Terminology:

  • Non-owning: A gsl::span<T> object is not responsible for the allocating or releasing the objects that it points to.
  • View: Object that encapsulates a reference type or points to existing objects that it does not own.
  • Buffer: Any memory region represented by the pair (pointer, size), pointer to beginning of the region and buffer size.

Summary:

  • gsl::span<T> can refer to any container: std::vector; std::array<T, size_t>; null-terminated character array; buffer, represented by pointer + tuple.
  • gsl::span<T> does not allocate any memory and copying it does not copy any element, gsl:span<T> is a view.

Use-cases for gsl::span<T>:

  • Provide range checking allowing to avoid buffer overrun (aka buffer overflow): terminates the program abnormally by calling std::terminate or throwing an exception (fail-fast approach). The range checking can be disabled for
  • Creating functions that can take std::vector, std::aray or a buffer which can be presented by a C-style aray or the pair (pointer, size).
  • Refactoring: Make C-style functions that take a buffer parameter represented by the pair (pointer, size) safer against buffer overflow vulnerabilities. The view object gsl::span<T> has built-in array-bound checking, any out-of-bounds access can either throw a gsl::fail_fast exception or a call std::terminate terminating the program abnormally.

Constructors

// --------- Constructors for dynamic-size span  -----------// 
gls::span(); 
gsL::span(nullptr_t) noexcept; 
gls::span(pointer ptr, index_type number_of_elements);
gls::span(pointer ptrFirstElement, ptrLastElement);

template <class Container> gls::span(Container& container);

// --------- Constructors for fixed-size span ---------------//

// Array buffer of size N. i.e:  int array [10] = { 1, 2, ..., 10};
template <size_t N> gsl::span(element_type (&array) [N]);

template <size_t N> gsl::span(Container& container);
template <size_t N> gsl::span(Container const& container);

Memeber Functions

  Return Function Description
  Type    
A index_type size() const Returns number of elements in the span
B index_type size_bytes() const Returns the total size of sequence in bytes
C TElement* data() const Returns pointer to first element of span.
D bool empty() const Returns treu if the span is empty
E TElement& operator[](index_type idx) const Returns reference to idx-th element of span.
F TElement& at(index_type idx) const Returns reference to idx-th element of span.
G gsl::span subspan(index_type offset, index_count n) Obtains a new span view from offset to (offset + n) elements.
       
H iterator being() const Returns iterator to beginning of span.
I iterator end() const Returns iterator to end of span.
       
J const_iterator cbegin() const  
L cosnt_iterator cend() const  
       
  • Note: E => The behavior of the operator (operator[])(index_type ) is undefined if idx index is out of range.
  • Note: F => Throws an exception if the index idx is out of range.

Dynamic-size gsl::span assignment

  • The variable sp of type gsl::span<int> can refere to fixed-size array; std::vector<int>; std::array<int, size_t> and so on.
  • assert(<PREDICATE>) => Terminates the program abnormally by calling std::abort if the predicate is false, then shows an error message indicating the file and line where the assertion has failed.
gsl::span<int> sp;

//--------- assign C-array to span -------------- // 
int carr [] = {-15, 6, 10, 9, 200};
sp = xs;                 // sp refers to array xs 
assert(sp[2] == 10); 
assert(sp[3] == 9);
assert(sp.size() == 5);
assert()

//------ Assign fixed-size buffer (C-array) to span ---// 
int buffer[200] = {3, 5, 6, 10, 20};
sp = buffer;
assert(sp.size() == 200);
assert(sp[2] == 6);
assert(sp[0] == 3);

//---- Assign to buffer defined by (pointer, size) tuple to span ---// 
int* p_buffer = &carr; 
auto buffer_size = gsl::index(5);
sp = gsl::make_span(p_buffer, buffer_size); 
assert(sp.size() == 5);
assert(sp[0] == -15);
assert(sp[2] ==  10);

//------ assign std::vector cointainer to span -------// 
std::vector<int> xs = {1, 2, 3, 4, 5};
sp = xs;                 // sp refers to vector xs 
assert(sp[2] == 3);
assrrt(sp.size() == 5);  

//------ assign std::array to span -------------------//
std::array<int, 6> arr = {10, 20, 30, 40, 50, 60}; 
sp = arr;  // sp refers to std::array arr 
assert(sp[2] == 30);
assert(sp.size() == 6)

Looping over gsl::span<T>

Dataset definition:

int dataset [] = {200, 100, -90, 89, 610, 53, 1365};
std::span<int> arrview;  
arrview = dataset; 

Index-based loop:

for(auto idx = gsl::index(0); idx < arr.size(); ++idx)
{
   std::cout << " Element[" << idx << "] =" << arrview[idx] << "\n";
}

Iterator-based loop:

for(auto it = arrview.begin(); it != arrview.end(); ++it)
{
   std::cout << *it << std::endl;
}

Iterator-based loop using std::begin() and std::end() functions:

for(auto it = std::begin(arrview); it != std::end(arrview); ++it)
{
   std::cout << *it << std::endl;
}

Range-based loop:

for(auto const& x:  arrview)
{
   std::cout << x << "\n";
}

Iteration with STL algorithms (header <algorithm>)

 std::for_each(arrview.begin(), arrview.end(), [](int n)
              {
                  std::cout << n << "\n";
              });

std::sort(arrview.begin(), arrview.end());

Function with gsl::span argument

A function that uses gsl::span<T> as argument can accept any buffer, C-arrray or continguous container as input.

int sum_span_element(gsl::span<int> xs)
{
    int sum = 0;
    for(auto x: xs){ sum += x; }
    return sum;
}

gsl::span<int> sp; 

// ------ Passing a C-array argument ------------------------------//
int carr [] = {-15, 6, 10, 9, 200};
sp = carr;
assert(sum_span_element(carr) == 210);
assert(sum_span_element(sp) == 210);

// ----- Passing a buffer argument defined by (pointer, size) pair ---// 
int* buffer_ptr  = carr; // Address of carr[0]
int  buffer_size = 5; 
sp = gsl::make_span(buffer_ptr, buffer_size);
assert( sum_span_element(sp) == 210 );
assert( sum_span_element(gsl::make_span(buffer_ptr, gsl::index(buffer_size))) == 210 );
assert( sum_span_element({buffer_ptr, gsl::index(buffer_size)}) == 210 );

// ---- Passing a std::vector argument ------------------//
std::vector<int> vec =  {10, 256, -15, 20, -8}; 
sp = vec; 
assert( sum_span_element(sp) == 263 );
assert( sum_span_element(vec) == 263 );

1.1.6 gsl::span code example

File: CMakeLists.txt

cmake_minimum_required(VERSION 3.14)
project(gls_experiment)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_VERBOSE_MAKEFILE ON)

# See documentation at: https://cmake.org/cmake/help/latest/module/FetchContent.html

#========== Macros for automating Library Fetching =============#
include(FetchContent)

# Download library archive (zip, *.tar.gz, ...) from URL
macro(Download_Library_Url NAME URL)
    FetchContent_Declare(${NAME} URL  ${URL})
    FetchContent_GetProperties(${NAME})
    message( [DOWNLOAD LIB] " VAR1 = ${${NAME}_SOURCE_DIR}")
    if(NOT ${NAME}_POPULATED)
        FetchContent_Populate(${NAME})
        add_subdirectory(${${NAME}_SOURCE_DIR} ${${NAME}_BINARY_DIR})
    endif()
endmacro()

#============== Library Download =========================#

Download_Library_Url( gsl
                      https://github.com/microsoft/GSL/archive/v2.0.0.zip
                     )

# ========= Targets Settings ==========================#

add_executable(microsft_gsl  microsft_gsl.cpp)
target_link_libraries(microsft_gsl GSL)

File: gsl_span1.cpp

#include <iostream>
#include <iomanip>
#include <memory>
#include <vector>
#include <array>
#include <algorithm>
#include <numeric>

// #define GSL_UNENFORCED_ON_CONTRACT_VIOLATION
// #define GSL_TERMINATE_ON_CONTRACT_VIOLATION
#define GSL_THROW_ON_CONTRACT_VIOLATION

// ------ Headers from the GSL - Guideline Support Library ----------//
//-------------------------------------------------------------------//
//#include <gsl/gsl> // For including everything
#include <gsl/gsl>
#include <gsl/span>
#include <gsl/pointers>

void display_span(const char* name, gsl::span<int> xs)
{
    std::cout << "  => " << name << " = [ ";
    for(auto const& x: xs){ std::cout << x << ", "; }
    std::cout <<  " ]\n";
}

template<typename T>
void disp(const char* label, T&& value)
{
    std::cout << std::setw(5)  << "  =>> "
              << std::setw(10) << label
              << std::setw(4) << "="
              << std::setw(6) << value
              << std::endl;
}

int cstring_len(gsl::not_null<const char*> ptr)
{
    int n = 0;
    for(auto p = ptr.get(); *p != '\0'; ++p) n++;
    return n;
}

int main()
{


    std::puts("\n ==== EXPERIMENT 1 - gsl::span variable =====================\n");
    {
        // Variable span, non owning view that can refere to any contiguous memory,
        // C-array buffers; buffers (pointer, size) tuple; std::vector; std::array
        gsl::span<int> sp;

        int carray [] = {10, 5, 8, 5, 6, 15};
        sp = carray;

        int* ptr_data = sp.data();

        display_span("carray [A]", sp);
        display_span("carray [B]", carray);
        disp(" *(carray.data + 0) ", *ptr_data);
        disp(" *(carray.data + 0) ", *(ptr_data + 1));


        // Display buffer defined by tuple (pointer, size)
        int*   buffer_ptr  = carray;
        size_t buffer_size = std::size(carray);
        sp = gsl::make_span(buffer_ptr, buffer_size);
        display_span("buffer", sp);

        std::vector<int> vector1 = {10, 256, -15, 20, -8};
        sp = vector1; // View refers to vector1
        display_span("vector1 [A]", sp);
        display_span("vector1 [B]", sp);

        // Just a nice C++ wrapper for a C stack-allocated or static-allocated array
        // It encapsulates the array: int [] std_array = {100, ...}
        std::array<int, 7> std_array = {100, -56, 6, 87, 61, 25, 151};
        sp = std_array;
        display_span("std_array [A]", sp);
        display_span("std_array [B]", sp);
    }

    std::puts("\n ==== EXPERIMENT 2 - gsl::span with std algorithms  ======\n");
    {
        int carray [] = {8, 10, 5, 90, 0, 14};
        gsl::span<int> sp;
        sp = carray;

        display_span("carray <before>", sp);
        std::sort(sp.begin(), sp.end());
        display_span("carray <after>", sp);

        auto sum = std::accumulate(sp.begin(), sp.end(), 0);
        std::cout << " [INFO] sum of all elements  = " << sum << std::endl;
    }

    std::puts("\n ==== EXPERIMENT 3 - gsl::span methods ==================\n");

     {
         int carrayA [] = {10, 15, -8, 251, 56, 15, 100};
         auto sp = gsl::span{carrayA};        

         std::cout << std::boolalpha;

         // Returns true if the view points to an empty location
         disp("sp.empty()", sp.empty());
         // Returns the number of elements
         disp("sp.size()", sp.size());
         // Returns the total size in bytes
         disp("sp.size_byte()", sp.size_bytes());                  

         // --- Access to elements with array-index operator [] -------//
         // Note: The bound checking only happens in the debug build.
         //       In the release build, it is disabled.
         disp("sp[0]", sp[0]);
         disp("sp[4]", sp[4]);
         disp("sp[5]", sp[5]);

         sp[0] = 100;

         // ---- Access to elements with always enabled, throws exception
         // if the access is out of bounds.
         disp("sp.at(0)", sp.at(0));
         disp("sp.at(4)", sp.at(5));
         disp("sp.at(5)", sp.at(4));

         // Note 1: The bound checking with array operator is only enabled
         // for debug building, in the release building it can be disabled
         // due to performance reasons.
         //
         // Note 2: It only throws exception, if the macro
         // GSL_THROW_ON_CONTRACT_VIOLATION is defined before GSL includes.
         // Otherwise, it calls std::terminate without throwing an exception.
         try
         {
             disp("sp[15]", sp[15]);
         } catch (gsl::fail_fast& ex)
         {
             std::cout << "  [ERROR] Failure = " << ex.what() << std::endl;
         }
     }


    return 0;
}

Output:

==== EXPERIMENT 1 - gsl::span variable =====================

 => carray [A] = [ 10, 5, 8, 5, 6, 15,  ]
 => carray [B] = [ 10, 5, 8, 5, 6, 15,  ]
 =>>  *(carray.data + 0)    =    10
 =>>  *(carray.data + 0)    =     5
 => buffer = [ 10, 5, 8, 5, 6, 15,  ]
 => vector1 [A] = [ 10, 256, -15, 20, -8,  ]
 => vector1 [B] = [ 10, 256, -15, 20, -8,  ]
 => std_array [A] = [ 100, -56, 6, 87, 61, 25, 151,  ]
 => std_array [B] = [ 100, -56, 6, 87, 61, 25, 151,  ]

==== EXPERIMENT 2 - gsl::span with std algorithms  ======

 => carray <before> = [ 8, 10, 5, 90, 0, 14,  ]
 => carray <after> = [ 0, 5, 8, 10, 14, 90,  ]
[INFO] sum of all elements  = 127

==== EXPERIMENT 3 - gsl::span methods ==================

 =>> sp.empty()   = false
 =>>  sp.size()   =     7
 =>> sp.size_byte()   =    28
 =>>      sp[0]   =    10
 =>>      sp[4]   =    56
 =>>      sp[5]   =    15
 =>>   sp.at(0)   =   100
 =>>   sp.at(4)   =    15
 =>>   sp.at(5)   =    56
 [ERROR] Failure = GSL: Precondition failure at ... .../_deps/gsl-src/include/gsl/span: 499

1.2 mapbox variant

Mapbox variant is a C++14,C++11 header-only library which provides variant type which are discriminant union containers (sum types), also called as algebraic data types in functional programming languages.

Repository:

Benefits:

  • Supports C++11 and C++14 compilers
  • Header only, however with fast compile-time.
  • Similar to Boost.Variant (unlike std::variant which is stack-allocated), but it is more convenient to use than Boost.Variant as the only way to get it is downloading and installing the whole Boost library.
  • Unlike, std::variant from C++17, which are stack allocated, mapbox variant can deal with non-complete recursive types as it is heap-allocated. In other words it can deal with recursive types without explicitly using std::unique_ptr, std::shared_ptr and so on.

Use cases:

  • Traverse recursive data structures, for instance: AST - Abstract Syntax Trees; Trees; Binary Trees; Json; XML; …;
  • Implement visitor object oriented design pattern.
  • Implement double dispatching.
  • Evaluate ASTs - Abstract Syntax Trees.
  • Emulate algebraic data types from functional programming languages.
  • Emulate pattern matching from functional programming languages.
  • Implementation of compilers, interpreters and parsers.

Sample Code

GIST:

File: CMakeLists.txt

cmake_minimum_required(VERSION 3.9)
project(recursive-variant)

#========== Global Configurations =============#
#----------------------------------------------#

set(CMAKE_CXX_STANDARD 17)     
set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# ------------ Download CPM CMake Script ----------------#

## Automatically donwload and use module CPM.cmake
file(DOWNLOAD https://raw.githubusercontent.com/TheLartians/CPM.cmake/v0.26.2/cmake/CPM.cmake
                 "${CMAKE_BINARY_DIR}/CPM.cmake")
include("${CMAKE_BINARY_DIR}/CPM.cmake")

#----------- Add dependencies --------------------------#

CPMAddPackage(
    NAME  mapbox-variant 
    URL   https://github.com/mapbox/variant/archive/v1.1.6.zip
    DOWNLOAD_ONLY YES
)

include_directories( ${mapbox-variant_SOURCE_DIR}/include )
message([TRACE] " mapbox source = ${mapbox-variant_SOURCE_DIR} ")

#----------- Set targets -------------------------------#

add_executable(app1 app1.cpp)
target_link_libraries(app1)

File: app1.cpp

#include <iostream>
#include <string> 
#include <sstream>
#include <cassert>

#include <mapbox/variant.hpp>

namespace m = mapbox::util;

// Forward declarations
struct Add;
struct Mul;

using expr = m::variant<int, m::recursive_wrapper<Add>, m::recursive_wrapper<Mul>>;

struct Add
{
    expr lhs;
    expr rhs;
    Add(expr lhs, expr rhs): lhs(lhs), rhs(rhs){ }
};

struct Mul
{
    expr lhs;
    expr rhs;
    Mul(expr lhs, expr rhs): lhs(lhs), rhs(rhs){ }
};

struct EvalVisitor
{
    int operator()(int x)
    {
        return x;
    }
    int operator()(Add const& v)
    {
        return m::apply_visitor(*this, v.lhs)
                + m::apply_visitor(*this, v.rhs);
    }

    int operator()(Mul const& v)
    {
        return m::apply_visitor(*this, v.lhs)
                * m::apply_visitor(*this, v.rhs);
    }

};


struct StringVisitor
{
    std::string format_expr(expr node)
    {
        if(node.is<int>())
            return std::to_string( node.get<int>() );
        else
            return std::string() + " (" + m::apply_visitor(*this, node) + ") ";
    }

    std::string operator()(int x)
    {
        return std::to_string(x);
    }
    std::string operator()(Add const& v)
    {
        return this->format_expr(v.rhs) + " + " + this->format_expr(v.lhs);
    }

    std::string operator()(Mul const& v)
    {
        return this->format_expr(v.rhs) + " * " + this->format_expr(v.lhs);
    }

};




int main(int argc, char** argv)
{
    std::cout << " [INFO] Running Ok " << std::endl;
    std::cout << std::boolalpha;

    // Abstract syntax tree
    expr ast;

    // Create visitor object
    auto eval = EvalVisitor{};
    auto disp = StringVisitor{};

    ast = 10;
    std::cout << " =>>  is_num? = " << ast.is<int>() << "\n";
    std::cout << " =>>    value = " << ast.get<int>() << "\n";
    std::cout << " =>>   Expr 0 = " << m::apply_visitor(disp, ast) << "\n";
    std::cout << " =>> Result 0 = " << m::apply_visitor(eval, ast) << '\n';

    ast = Mul(5, Add(3, 4));
    std::cout << "\n";
    std::cout << " =>>  is_num? = " << ast.is<int>() << "\n";
    std::cout << " =>>  is_add? = " << ast.is<Mul>() << "\n";
    std::cout << " =>>   Expr 1 = " << m::apply_visitor(disp, ast) << "\n";
    std::cout << " =>> Result 1 = " << m::apply_visitor(eval, ast) << '\n';

    // (10 + 4) * (5 * (3 + 8)) = 770
    ast        = Mul(Add(10, 4), Mul(5, Add(3, 8)));
    std::cout << "\n";
    std::cout << " =>>  is_mul? = " << ast.is<Mul>() << "\n";
    std::cout << " =>>   Expr 2 = " << m::apply_visitor(disp, ast) << "\n";
    int result = m::apply_visitor(eval, ast);
    assert(result == 770);
    std::cout << " =>> Result 2 = " << result  << '\n';


    return 0;
}

Output:

[INFO] Running Ok 
=>>  is_num? = true
=>>    value = 10
=>>   Expr 0 = 10
=>> Result 0 = 10

=>>  is_num? = false
=>>  is_add? = true
=>>   Expr 1 =  (4 + 3)  * 5
=>> Result 1 = 35

=>>  is_mul? = true
=>>   Expr 2 =  ( (8 + 3)  * 5)  *  (4 + 10) 
=>> Result 2 = 770

1.3 Range v3 (C++20)

Ranges v3 is a header-only generic library that will be included in the C++20 ranges and provides stl-like algorithms that can operate on ranges, also known as pairs of iterators. The benefit of the range v3 library over STL algorithms and iterator pairs is that the greater composability and that the calling code does not need to specify the iterator pair explicitly.

Resources:

Headers:

  • Include all features:
    • <range/v3/all.hpp> (Note: It increase the compile-time)
  • Algorithms
    • <range/v3/action/join.hpp>
    • <range/v3/algorithm/copy.hpp>
    • <range/v3/algorithm/for_each.hpp>
    • <range/v3/algorithm/mismatch.hpp>
  • All
    • <range/v3/view/transform.hpp>
    • <range/v3/core.hpp>
    • <range/v3/view/all.hpp>
    • <range/v3/view/concat.hpp>
    • <range/v3/view/group_by.hpp>
    • <range/v3/view/iota.hpp>
    • <range/v3/view/join.hpp>
    • <range/v3/view/repeat_n.hpp>
    • <range/v3/view/single.hpp>
    • <range/v3/view/take.hpp>
    • <range/v3/view/transform.hpp>

Built-in Range Views:

  • Views are lazily evaluated algorithms that are computed on demand without any wasteful allocation.
adjacent_remove_if drop_while map split
all empty move stride
any_range filter parital_sum tail
bounded for_each remove_if take
c_str generate repeat take_exactly
chunck generate_n repetat_n take_while
concat group_by replace tokenize
const_ indirect replace_if transform
counted interspece reverse unbounded
delimit iota single unique
drop join slice zip_with

Built-in Range Actions:

  • Actions are eager sequence algorithms that operates on containers and returns containers.
drop push_front stable_sort
drop_while remove_if stride
erase shuffle take
insert slice take_while
join sort transform
push_back split unique

Comparison Range Views X Range Actions:

Range Views Range Actions
Lazy sequence algorithms Eager sequence algorithms
Lightweightm, non owning Operates on containers and returns containers.
Doesn't perform allocation Performs allocation
Composable Composable
Non-mutating Potentially mutating

Example:

Main Function - Range Algorithms Experiments

  • Experiment 1A:
    std::cout << "   +------------------------------------+\n"
              << "   | Range Algorithms Experiments       |\n"
              << "   +------------------------------------+\n";

    auto printLambda = [](auto x){
        std::cout << " x = " << x << std::endl;
    };

    // std::cout << view::iota(2, 10) << std::endl;

    //-------------------------------------------//
    std::puts("\n === EXPERIMENT 1 A - for_each ===");
    {

        std::vector<int> xs = {8, 9, 20, 25};
        std::cout << " => Print numbers" << std::endl;
        ranges::for_each(xs, printLambda);

        std::deque<std::string> words = {"c++", "c++17", "C++20", "asm", "ADA"};
        std::cout << " => Print words" << std::endl;
        ranges::for_each(xs, printLambda);
}

Output:

  +------------------------------------+
  | Range Algorithms Experiments       |
  +------------------------------------+

=== EXPERIMENT 1 A - for_each ===
=> Print numbers
x = 8
x = 9
x = 20
x = 25
=> Print words
x = 8
x = 9
x = 20
x = 25
  • Experiment 1B
std::puts("\n === EXPERIMENT 1 B - for_each ===");
{
    std::string astr = "bolts";
    ranges::for_each(astr, printLambda);
}

Output:

=== EXPERIMENT 1 B - for_each ===
x = b
x = o
x = l
x = t
x = s
  • Experiment 2
std::puts("\n === EXPERIMENT 2 - sort ===");
{
    std::vector<double> xs2 = {10.45, -30.0, 45.0, 8.2, 100.0, 10.6};
    std::cout << " Reverse vector" << std::endl;
    std::cout << " BEFORE xs2 = " << xs2 << std::endl;
    ranges::sort(xs2);
    std::cout << " AFTER xs2 = " << xs2 << std::endl;
}

Output:

=== EXPERIMENT 2 - sort ===
Reverse vector
BEFORE xs2 = [6]( 10.45 -30 45 8.2 100 10.6  )
AFTER xs2 = [6]( -30 8.2 10.45 10.6 45 100  )
  • Experiment 3
std::puts("\n === EXPERIMENT 3 - fill ===");
{
    std::vector<int> xs2(10, 0);

    std::cout << " BEFORE  A xs2 = " << view::all(xs2 )<< std::endl;
    std::cout << " BEFOREB B xs2 = " << xs2 << std::endl;

    ranges::fill(xs2, 10);
    std::cout << " AFTER 1 => x2 = " << xs2 << std::endl;

    ranges::fill(xs2, 5);
    std::cout << " AFTER 2 => x2 = " << xs2 << std::endl;
}

Output:

=== EXPERIMENT 3 - fill ===
BEFORE  A xs2 = [0,0,0,0,0,0,0,0,0,0]
BEFOREB B xs2 = [10]( 0 0 0 0 0 0 0 0 0 0  )
AFTER 1 => x2 = [10]( 10 10 10 10 10 10 10 10 10 10  )
AFTER 2 => x2 = [10]( 5 5 5 5 5 5 5 5 5 5  )
  • Experiment 4:
std::puts("\n === EXPERIMENT 4 - reverse ===");
{
    std::deque<std::string> words = {"c++", "c++17", "C++20", "asm", "ADA"};

    std::cout << " BEFORE => words = " << view::all(words) << std::endl;

    ranges::reverse(words);
    std::cout << " AFTER => words = " << view::all(words) << std::endl;
}

Output:

=== EXPERIMENT 4 - reverse ===
BEFORE => words = [c++,c++17,C++20,asm,ADA]
AFTER => words = [ADA,asm,C++20,c++17,c++]
  • Experiment 5:
std::puts("\n === EXPERIMENT 5 - remove_if ===");
{
    std::vector<int> xvec = {1, 2, 5, 10, 1, 4, 1, 2, 8, 20, 100, 10, 1};

    std::cout << " BEFORE => xvec = " << xvec << std::endl;

    // Erase remove idiom
    xvec.erase(ranges::remove_if(xvec, [](int x){ return x == 1; }), xvec.end());
    std::cout << " AFTER  => xvec = " << xvec << std::endl;
}

Output:

=== EXPERIMENT 5 - remove_if ===
BEFORE => xvec = [13]( 1 2 5 10 1 4 1 2 8 20 100 10 1  )
AFTER  => xvec = [9]( 2 5 10 4 2 8 20 100 10  )
  • Experiment 6:
std::puts("\n === EXPERIMENT 6 - find ===");
{
    std::vector<int> xvec = {1, 2, 5, 10, 1, 4, 1, 2};

    auto it = ranges::find(xvec, 10);
    if(it != xvec.end()){
         std::cout << " [OK] Found value == 10 => *it = " << *it << std::endl;
         std::cout << " Position = "
                   << ranges::distance(xvec.begin(), it)
                   << std::endl;
    }
    else
        std::cout << " [FAIL] Not found  value " << std::endl;
}

Output:

=== EXPERIMENT 6 - find ===
[OK] Found value == 10 => *it = 10
Position = 3
  • Experiment 7:
std::puts("\n === EXPERIMENT 7 - accumulator ===");
{
    std::vector<int> xvec = {1, 2, 5, 10, 1, 4, 1, 2};

    int result = ranges::accumulate(xvec, 0);
    std::cout << " => ranges::accumulate(xvec, 0) = "
              << result << std::endl;

}

Output:

=== EXPERIMENT 7 - accumulator ===
=> ranges::accumulate(xvec, 0) = 26

Range View Experiments

  • Experiment 1A
std::cout << "\n";
std::cout << "  +------------------------------------+\n"
          << "   | Range View Experiments             |\n"
          << "   +------------------------------------+\n";

std::puts("\n === EXPERIMENT 1A - view::all");
{
    std::vector<char> xs = {'x', 'y', 'm', 'k', 'm'};

    std::cout << " [1] => std::view(xs) = "
              << view::all(xs) << std::endl;

    std::cout << "\n [2] => xs = ";
    for(auto&& x: view::all(xs))
        std::cout << x << " ";
    std::cout << std::endl;
}

Output:

=== EXPERIMENT 1A - view::all
[1] => std::view(xs) = [x,y,m,k,m]

[2] => xs = x y m k m
  • Experiment 1B
std::puts("\n === EXPERIMENT 1B - view::reverse ===");
{
    std::vector<char> xs = {'x', 'y', 'm', 'k', 'm'};

    for(auto const& x: view::reverse(xs))
        std::cout << " " << x;

    std::cout << std::endl;
    std::cout << " AFTER xs = " << xs << std::endl;
}

Output:

=== EXPERIMENT 1B - view::reverse ===
m k m y x
AFTER xs = [5]( x y m k m  )
  • Experiment 1C:
std::puts("\n === EXPERIMENT 1C - view::transform ===");
{
    std::vector<int> xs = {100, 5, 20, 9, 10, 6};
    auto a_lambda = [](auto x){ return 5 * x -10; };

    for(auto const& x: xs | view::transform(a_lambda))
        std::cout << " " << x;

    std::cout << std::endl;
    //std::cout << " AFTER xs = " << xs << std::endl;
}

Output:

=== EXPERIMENT 1C - view::transform ===
490 15 90 35 40 20
  • Experiment 2A:
std::puts("\n === EXPERIMENT 2A - Range adaptors pipeline ===");
{
    auto a_lambda = [](auto x){ return 5 * x -10; };

    std::cout << " => Iota view = ";
    for(auto const& x: view::iota(1) | view::take(8))
        std::cout << " " << x;
    std::cout << std::endl;
}

Output:

=== EXPERIMENT 2A - Range adaptors pipeline ===
=> Iota view =  1 2 3 4 5 6 7 8
  • Expeirment 2B
std::puts("\n === EXPERIMENT 2B - Range adaptors pipeline ===");
{
    std::cout << " => Iota view [B] = \n";
    auto aview =
              view::iota(1)
            | view::take(5)
            | view::transform([](int x){ return 5 * x + 6; });

    std::cout << " [INFO] aview = " << aview << std::endl;
    std::cout << " [INFO] aview | reverse = "
              << (aview | view::reverse)
              << std::endl;

    std::cout << "\n Iteration 1 => ";
    ranges::for_each(aview, [](int a){ std::cout << a << " "; });

    std::cout << "\n Iteration 2 => ";
    for(auto const& x: aview | view::reverse)
        std::cout << x << " ";
    std::cout << std::endl;

}

Output:

=== EXPERIMENT 2B - Range adaptors pipeline ===
=> Iota view [B] = 
[INFO] aview = [11,16,21,26,31]
[INFO] aview | reverse = [31,26,21,16,11]

Iteration 1 => 11 16 21 26 31 
Iteration 2 => 31 26 21 16 11 
  • Experiment 3A
std::puts("\n === EXPERIMENT 3A - enumerate ===");
{
    std::deque<std::string> words = { "c++", "c++17", "C++20", "asm" };

    std::cout << " ==== Loop 1 ==== " << std::endl;
    for(auto const& x: view::enumerate(words))
        std::cout << " => n = " << x.first
                  << " ; w = " << x.second << std::endl;

    std::cout << " ==== Loop 2 ==== " << std::endl;
    for(auto const& x: words | view::enumerate)
        std::cout << " => n = " << x.first
                  << " ; w = " << x.second << std::endl;
}

Output:

=== EXPERIMENT 3A - enumerate ===
==== Loop 1 ==== 
=> n = 0 ; w = c++
=> n = 1 ; w = c++17
=> n = 2 ; w = C++20
=> n = 3 ; w = asm
==== Loop 2 ==== 
=> n = 0 ; w = c++
=> n = 1 ; w = c++17
=> n = 2 ; w = C++20
=> n = 3 ; w = asm
  • Experiment 4A:
std::puts("\n === EXPERIMENT 4 - ranges::accumulate withe iterator ===");
{
    auto aview = view::iota(2)
               | view::transform([](double x){return 3.0 * x - 5; })
               | view::take(15);

    std::cout << " aview = " << aview << std::endl;
    std::cout << " accumulate(aview) = " << ranges::accumulate(aview, 0.0)
              << std::endl;
}

Output:

=== EXPERIMENT 4 - ranges::accumulate withe iterator ===
aview = [1,4,7,10,13,16,19,22,25,28,31,34,37,40,43]
accumulate(aview) = 330
  • Experiment 5
std::puts("\n === EXPERIMENT 5 - Copy Range to destination ===");
{
    std::vector<int> output;
    auto aview = view::iota(5)
               | view::transform([](int n){ return 6 * n - 10;})
               | view::take(10);

    std::cout << " BEFORE => output = " << output << std::endl;

    ranges::copy(aview, ranges::back_inserter(output));
    std::cout << " AFTER => output = " << output << std::endl;
}

Output:

=== EXPERIMENT 5 - Copy Range to destination ===
BEFORE => output = [0](  )
AFTER => output = [10]( 20 26 32 38 44 50 56 62 68 74  )

1.4 Printf replacements

1.4.1 fmtlib (fmt) - Better printf (C++20)

Fmt is a highly popular library for printing in similar way to the old C's printf. The advantage of fmt over old printf are the type safety and concise format specifiers based on Python ones. This library features will be included in the C++20 upcoming standard library. Nevertheless, most compilers still does not implement this library.

  • The old C's printf is not type-safe and prone to security vulnerabilities if proper care is not taken.

Web Site:

Repository:

C++20 Stadnard library inclusion:

Conan Refence:

See:

Library Local Installation for header-only usage

Installation at: ~/dev/include/fmt

$ git clone https://github.com/fmtlib/fmt

# Build directory /home/<USER>/dev on Linux 
$ mkdir -p ~/dev && cd dev                           
# Clone repository 
$ git clone https://github.com/fmtlib/fmt
# Extract headers 
$ cp -r -v fmt/include ~/dev/
# Delete fmt directory 
$ rm -rf fmt 

Testing code: File - fmttest.cpp

#include <iostream>
#include <fstream>
#include <cmath>
#include <sstream>

// If defined before including fmt, uses it as header-only library
#define FMT_HEADER_ONLY

// Basic functionality
#include <fmt/core.h>
// fmt string literals "name"_a, "product"_a
#include <fmt/format.h>
// Print to streams std::cout, std::cerr, std::ostream
#include <fmt/ostream.h>

#include <fmt/color.h>

// #include <fmt/color.h>

using namespace fmt::literals;

void printHeader(const char* header)
{
    fmt::print("\n{}\n", header);
    fmt::print("---------------------------------------------\n");
}

int main()
{
    printHeader(" ***** EXPERIMENT 1 - Boolean  *************");
    fmt::print(" true == {0} ; false == {1} \n", true, false);


    printHeader(" ***** EXPERIMENT 2 - Numeric Base  *********");

    fmt::print(" [BASES] => dec: {0:d} ; hex = 0x{0:X} "
               " ; oct = {0:o} ; bin = 0b{0:b} \n", 241 );

    printHeader(" ***** EXPERIMENT 3 - Positional arguments ****");

    fmt::print(" first = {0}, 2nd = {1}, 1st = {1}, 3rd = {2}\n",
                200, "hello", 5.615);

    printHeader("**** EXPERIMENT 4 - Named Arguments ********* ");

    fmt::print(" [A] Product => product = {0} id = {1} price = {2}\n"
               ,"oranges 1 kg", 200, 10.6758 );

    // Requires: #include <fmt/format.h>
    // using namespace fmt::literals;
    fmt::print(" [B] Product => product = {product} id = {id} price = {price:.2F}\n"
               , "product"_a = "oranges 1 kg", "id"_a = 200, "price"_a = 10.6758 );

    fmt::print(" [B] Product => product = {product} id = {id} price = {price:.2F}\n"
               , fmt::arg("product", "oranges 1 kg")
               , fmt::arg("id",  200)
               , fmt::arg("price", 10.6758 ));

    printHeader("************ Colored Output ******************");

    fmt::print( fmt::fg(fmt::color::aqua) | fmt::emphasis::bold,
               " [INFO] Voltage Leval  = {0:+.3F}\n", 10.6478);

    fmt::print( fmt::fg(fmt::color::red) | fmt::emphasis::underline,
               " [ERROR] Fatal Error, shutdown systems code  0x{0:X}\n", 2651);


    printHeader(" ***** EXPERIMENT 5 - Numeric Formatting ******");

    double x = 20.0;
    fmt::print("The square root of x = {}\n", std::sqrt(x));

    x = 28524.0;

    fmt::print(" log(x) = {:.2F}  (2 digit precision)\n", std::log(x));
    fmt::print(" log(x) = {:+.6F} (6 digit precision)\n", std::log(x));
    fmt::print(" 2000 * log(x) = {:+.6G} (6 digit precision)\n", 1e5 * std::log(x));
    fmt::print("  log(x) = {0:+.8E} ; sqrt(x) = {1:+8E} (8 digit precision)\n",
               std::log(x), std::sqrt(x));

    printHeader(" ***** EXPERIMENT 6 - Print numeric table ******");

    int i = 0;
    for(double x = 0.0; x <= 4.0; x += 0.5)
        fmt::print("{0:8d}{1:10.5F}{2:10.5F}\n", i++, x, std::exp(x));

    printHeader(" ***** EXPERIMENT 7 - Print table to file *******");

    // std::ofstream file("/tmp/table.txt");
    std::stringstream file; // Fake file

    i = 0;

    // Note: Requires <fmt/ostream.h>
    for(double x = -4.0; x <= 4.0; x += 1.0)
        fmt::print(file, "{0:8d}{1:10.5F}{2:10.5F}\n", i++, x, std::exp(x));

    fmt::print("File content = \n{0}", file.str());

    return 0;
}

Program output:

 ***** EXPERIMENT 1 - Boolean  *************
---------------------------------------------
 true == true ; false == false 

 ***** EXPERIMENT 2 - Numeric Base  *********
---------------------------------------------
 [BASES] => dec: 241 ; hex = 0xF1  ; oct = 361 ; bin = 0b11110001 

 ***** EXPERIMENT 3 - Positional arguments ****
---------------------------------------------
 first = 200, 2nd = hello, 1st = hello, 3rd = 5.615

**** EXPERIMENT 4 - Named Arguments ********* 
---------------------------------------------
 [A] Product => product = oranges 1 kg id = 200 price = 10.6758
 [B] Product => product = oranges 1 kg id = 200 price = 10.68
 [B] Product => product = oranges 1 kg id = 200 price = 10.68

************ Colored Output ******************
---------------------------------------------
 [INFO] Voltage Leval  = +10.648
 [ERROR] Fatal Error, shutdown systems code  0xA5B

 ***** EXPERIMENT 5 - Numeric Formatting ******
---------------------------------------------
The square root of x = 4.47213595499958
 log(x) = 10.26  (2 digit precision)
 log(x) = +10.258501 (6 digit precision)
 2000 * log(x) = +1.02585e+06 (6 digit precision)
  log(x) = +1.02585011E+01 ; sqrt(x) = +1.688905E+02 (8 digit precision)

 ***** EXPERIMENT 6 - Print numeric table ******
---------------------------------------------
       0   0.00000   1.00000
       1   0.50000   1.64872
       2   1.00000   2.71828
       3   1.50000   4.48169
       4   2.00000   7.38906
       5   2.50000  12.18249
       6   3.00000  20.08554
       7   3.50000  33.11545
       8   4.00000  54.59815

 ***** EXPERIMENT 7 - Print table to file *******
---------------------------------------------
File content = 
       0  -4.00000   0.01832
       1  -3.00000   0.04979
       2  -2.00000   0.13534
       3  -1.00000   0.36788
       4   0.00000   1.00000
       5   1.00000   2.71828
       6   2.00000   7.38906
       7   3.00000  20.08554
       8   4.00000  54.59815

Compilation:

$ g++  fmttest.cpp -o app.bin -std=c++1z -Wall -I$HOME/dev/include 

# Or, on Linux
$ clang++  fmttest.cpp -o app.bin -std=c++1z -Wall -I/home/<USER>/dev/include 

# Or, On OSX 
$ clang++  fmttest.cpp -o app.bin -std=c++1z -Wall -I/Users/<USER>/dev/include 

The code can be compiled without specifying the include path, by adding the following code to the file ~/.profile on Linux.

#------------ Local Libraries Installation ---------------------
LOCAL_LIB_PATH=~/dev
export CPLUS_INCLUDE_PATH=$LOCAL_LIB_PATH/include:$CPLUS_INCLUDE_PATH
export C_INCLUDE_PATH=$LOCAL_LIB_PATH/include:$C_INCLUDE_PATH
export LIBRARY_PATH=$LOCAL_LIB_PATH/lib:$LIBRARY_PATH
export LD_LIBRARY_PATH=$LOCAL_LIB_PATH/lib:$LD_LIBRARY_PATH

After this configuration was set, the code can be compiled with:

$ g++  fmttest.cpp -o app.bin -std=c++1z -Wall

# Header only compilation takes 2 seconds 
$ time g++  fmttest.cpp -o app.bin -std=c++1z -Wall

real    0m1.824s
user    0m1.650s
sys     0m0.156s

Once the environment variabble CPLUS_INCLUDE_PATH is set, the library can be loaded from CERN's Root or Cling REPL with:

#define FMT_HEADER_ONLY
#include <fmt/format.h>
#include <fmt/color.h>
#include <fmt/ostream.h>

>> fmt::print(" x = {0:.5F}, sqrt(x) = {1:+.8F}\n", 20.6, std::sqrt(20.6))
 x = 20.60000, sqrt(x) = +4.53872229
>> 

// Print with color foreground blue 
>> fmt::print(fmt::fg(fmt::color::blue), " [INFO] x = {0:.5E}\n", 20.6)
 [INFO] x = 2.06000E+01

// Print with background color blue 
>> fmt::print(fmt::bg(fmt::color::blue), " [INFO] x = {0:.5E}\n", 20.6)
 [INFO] x = 2.06000E+01
>> 

1.4.2 tinyprintf

Single-file header-only library replacement for printf. The advatange of this library is the extensibility, easy-of-use and deployment as it just a single header-file.

Repository:

Sample Project:

File: CMakeLists.txt

cmake_minimum_required(VERSION 2.8)
project(tinyprintf-test)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_VERBOSE_MAKEFILE ON)

#============= Functions and macros ===========================#
macro(Download_Single_Headerlib FILE URL)
    file(DOWNLOAD ${URL} ${CMAKE_BINARY_DIR}/include/${FILE})
    IF(NOT Download_Single_Headerlib_flag)
       include_directories(${CMAKE_BINARY_DIR}/include)
       set(Download_Single_Headerlib_flag TRUE)
    ENDIF()
endmacro()

Download_Single_Headerlib(
    tinyformat.h
    "https://raw.githubusercontent.com/c42f/tinyformat/master/tinyformat.h"
    )

#============ Target settings ==================================#
add_executable(main main.cpp)

File: main.cpp

#include <iostream>
#include <string>
#include <cmath>
#include <vector>

#include <tinyformat.h>

template<typename T>
void display(T&& x)
{
    tfm::printfln(" Function = %s ; Value x = %s", __FUNCTION__, x);
}

struct Point3D
{
    double x, y, z;
};

/** Enables printing user defined data with tinyprintf library */
std::ostream&
operator<<(std::ostream& os, Point3D const& point )
{
    auto [x, y, z] = point;
    return os << " Point3d{ x = " << x
              << " ; y = " << y << " ; z = "
              << z << " } ";
}

int main(int argc, char** argv)
{
    std::string s = "Hello world";
    tfm::printf(" LINE 1 => s = %s n1 = %d f = %.3f \n", s, 10, 283.41345);

    tfm::printfln(" LINE 2 => arg1 = %1$s ; arg3 = %3$s ; arg2 = %2$d ", "A1", 1003, "A3");

    // Templated function
    display(10);
    display("Hello world C++20 ... modules");

    // Print user-defined type
    tfm::printfln("\n User Defined Data =>> = %s", Point3D{3.5, 100.34, -90.341});

    //==== Print to stream ===========//
    std::stringstream ss;
    for(int i = 0; i < 5; i++)
    {
        tfm::format(ss, "%8.d %10.5f\n", i, std::exp(i));
    }

    tfm::printfln("\nFunction tabulation result => \n %s", ss.str());

    return 0;
}

Output of executable main:

 % ./main 

 LINE 1 => s = Hello world n1 = 10 f = 283.413 
 LINE 2 => arg1 = A1 ; arg3 = A3 ; arg2 = 1003 
 Function = display ; Value x = 10
 Function = display ; Value x = Hello world C++20 ... modules

 User Defined Data =>> =  Point3d{ x = 3.5 ; y = 100.34 ; z = -90.341 } 

Function tabulation result => 
        0    1.00000
       1    2.71828
       2    7.38906
       3   20.08554
       4   54.59815

1.5 Pretty Printing

1.5.1 cxx-prettyprint - STL container pretty print

Description:

  • "A header-only library for C++(0x) that allows automagic pretty-printing of any container."
  • Note: as this library is single-file and header-only, it does not need any pre-compilation. All that is needed for using it is to download the file prettyprint.hpp and add it to the project directory or any other include directory.

Website:

Repository:

Examples in CERN-Root REPL

Step 1: Download the library and start the CERN's ROOT Repl (Cling).

$ curl -O -L https://raw.githubusercontent.com/louisdx/cxx-prettyprint/master/prettyprint.hpp

$ ~/opt/root/bin/root.exe
   ------------------------------------------------------------
  | Welcome to ROOT 6.14/04                http://root.cern.ch |
  |                               (c) 1995-2018, The ROOT Team |
  | Built for linuxx8664gcc                                    |
  | From tags/v6-14-04@v6-14-04, Aug 23 2018, 17:00:44         |
  | Try '.help', '.demo', '.license', '.credits', '.quit'/'.q' |
   ------------------------------------------------------------

Include header in the repl:

>> #include "prettyprint.hpp""

Print vector:

>> auto xs = std::vector<double>{434.4, -10.54, 9.654, 45.23, -10.56};

//------- Print Vector -----------------//
>>
>> std::cout << " xs = " << xs << std::endl;
 xs = [5](434.4 -10.54 9.654 45.23 -10.56 )
>>

Print tuple:

>> auto t = std::make_tuple(std::string("hello"), 100)
(std::tuple<basic_string<char>, int> &) { "hello", 100 }

>> std::cout << " t = " << t << std::endl;
 t = (hello, 100)

>> auto tt = std::make_tuple(std::string("hello"), 100, 'x')
(std::tuple<basic_string<char>, int, char> &) { "hello", 100, 'x' }
>>
>> std::cout << " tt = " << tt << std::endl;
 tt = (hello, 100, x)

Print map:

>> std::map<std::string, double> dataset {{"USD", 200.3}, {"BRL", 451.34}, {"CAD", 400.5}, {"AUD", 34.65}};

>> std::cout << " dataset = " << dataset << std::endl;
 dataset = [(AUD, 34.65), (BRL, 451.34), (CAD, 400.5), (USD, 200.3)]
>>

1.5.2 pprint - Pretty print library for C++17

PPrint is an easy and simple to use header-only pretty printing library for C++17 capable of printing all C++17 containers, including variants and optional in a nice way.

Repository:

Download the library:

$ cd <PROJECT> 
$ curl -O -L https://raw.githubusercontent.com/p-ranav/pprint/v0.9.1/include/pprint.hpp

File: main.cpp

#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <complex>

// Library from: https://github.com/p-ranav/pprint/tree/v0.9.1
#include <include/pprint.hpp>

using cpl = std::complex<double>;

int main()
{
    auto printer = pprint::PrettyPrinter{};

    std::puts("\n========= Print Numbers ============");
    printer.print(100);
    printer.print(59.15);

    std::puts("\n======= Print string with/without quotes =====");
    printer.print(" Testing CEE PLUS PLUS Printer");

    printer.quotes(false);
    printer.print(" Testing CEE PLUS PLUS Printer");
    printer.quotes(true);

    std::puts("\n=========== Print booleans ===========");
    printer.print(true);
    printer.print(false);

    std::puts("\n========== Print Null Pointer =========");
    printer.print(nullptr);

    std::puts("\n======== Print complex numbers =========");
    cpl x1{10.0, 15};
    auto x2 = cpl{-36.34, 98.765};
    cpl x3 = {-2.5312, -9.81};
    printer.quotes(false);
    printer.print("x1 = ", x1, " ; x2 = ", x2, " ; x3 = ", x3);

    std::puts("\n======= STL Container =================");

    std::puts(" --->> Print a vector <<----- ");

    std::vector<std::string> words = { "c++", "Ada", "Scala", "C++17", "Serial" };
    printer.print(words);

    std::puts(" --->> Print a map <<--------");
    std::map<std::string, double> dataset = { {"x", -9.4351}, {"mx", -100.35}, {"g", 9.814}  };
    printer.print(dataset);


    std::puts(" --->> Print a map / Compact <<--------");
    printer.compact(true);
    printer.print(dataset);


    std::puts(" -->> Print a vector of tuples <<------");
    printer.compact(false);
    auto xlist = std::vector<std::tuple<std::string, int>>{
        {"CAD", 100}, {"AUD", 900 }, {"BRL", 871}, {"EUR", 9871}
    };

    printer.print(xlist);

    std::puts("\n=== Print C++17 Variants =================");
    using var = std::variant<int, std::string, std::vector<int>>;
    std::vector<var> varlist = { 100, "hello", 51, std::vector{23, 100, -9, 8, 100}, "world" };
    printer.print(varlist);

    return 0;
}

Output:

./main.bin 

========= Print Numbers ============
100
59.15

======= Print string with/without quotes =====
" Testing CEE PLUS PLUS Printer"
 Testing CEE PLUS PLUS Printer

=========== Print booleans ===========
true
false

========== Print Null Pointer =========
nullptr

======== Print complex numbers =========
x1 =  (10 + 15i)  ; x2 =  (-36.34 + 98.765i)  ; x3 =  (-2.5312 + -9.81i)

======= STL Container =================
 --->> Print a vector <<----- 
[
  c++, 
  Ada, 
  Scala, 
  C++17, 
  Serial

]
 --->> Print a map <<--------
{
  g : 9.814, 
  mx : -100.35, 
  x : -9.4351
}
 --->> Print a map / Compact <<--------
{g : 9.814,   mx : -100.35, x : -9.4351}
 -->> Print a vector of tuples <<------
[
  ("CAD", 100), 
  ("AUD", 900), 
  ("BRL", 871), 
  ("EUR", 9871)
]

=== Print C++17 Variants =================
[
  100, 
  hello, 
  51, 
  [23, 100, -9, 8, 100], 
  world
]

1.6 Command Line Parsing

1.6.1 CLI11 Library

CLI11 is a small and lightweight header-only library for command line parsing.

Repository:

Documentation:

Doxygen API docs:

Conan reference:

Sample Project:

File: CMakeLists.txt

cmake_minimum_required(VERSION 2.8)
project(cli11-app)

#========================================#

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_VERBOSE_MAKEFILE ON)

#=========== Conan Bootstrap =================#

# Download automatically, you can also just copy the conan.cmake file
if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake")
   message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan")
   file(DOWNLOAD "https://github.com/conan-io/cmake-conan/raw/v0.13/conan.cmake"
                 "${CMAKE_BINARY_DIR}/conan.cmake")
endif()

include(${CMAKE_BINARY_DIR}/conan.cmake)
set(CONAN_PROFILE default)

conan_cmake_run( REQUIRES
                 CLI11/1.8.0@cliutils/stable
                 BASIC_SETUP
                 BUILD missing )

#=========== Find Package ================#

find_package(CLI11 REQUIRED)

#=========== Targets ======================#

# Note: CLI11 is a header-only library and does not need Linking
add_executable(httpserver main.cpp)

File: main.cpp

#include <iostream>
#include <fstream>
#include <string>

#include <CLI/CLI.hpp>

int main(int argc, char** argv)
{
    CLI::App app{ "C++ http Web Server"};
    app.footer("\n Creator: Somebody else.");

    // Sets the current path that will be served by the http server
    std::string dir = "default";
    app.add_option("directory", dir, "Directory served")->required();

    // Sets the port that the server will listen to
    int port = 8080;
    app.add_option("-p,--port", port, "TCP port which the server will bind/listen to");

    // Set the the hostname that the server will listen to
    // Default: 0.0.0.0 => Listen all hosts
    std::string host = "0.0.0.0";
    app.add_option("--host", host, "Host name that the sever will listen to.");

     app.validate_positionals();
    CLI11_PARSE(app, argc, argv);

    std::cout << "Running server at port = " << port
              << "\n and listen to host = " << host
              << "\n serving directory = " << dir << "\n";

    return 0;
}

Sample program output:

  • Display help:
$ ./httpserver -h
C++ http Web Server
Usage: ./httpserver [OPTIONS] directory

Positionals:
  directory TEXT REQUIRED     Directory served

Options:
  -h,--help                   Print this help message and exit
  -p,--port INT               TCP port which the server will bind/listen to
  --host TEXT                 Host name that the sever will listen to.

 Creator: Somebody else.
  • Run app.
$ ./httpserver 
directory is required
Run with --help for more information.

$ ./httpserver /var/data/www
Running server at port = 8080
 and listen to host = 0.0.0.0
 servind directory = /var/data/www


$ ./httpserver /var/data/www --port=9090
Running server at port = 9090
 and listen to host = 0.0.0.0
 serving directory = /var/data/www


$ ./httpserver --port=9090 --host=localhost /home/user/pages
Running server at port = 9090
 and listen to host = localhost
 serving directory = /home/user/pages

1.6.2 Clipp library

Single-file header-only library for parsing command line arguments (command line option parsing). It supports: positional values (required arguments); optional values; manpage generation and so on.

Sample Project

File: CMakeLists.txt

cmake_minimum_required(VERSION 3.0)
project(clipp-test)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_VERBOSE_MAKEFILE ON)

#============= Functions and macros ===========================#

macro(Download_Single_Headerlib FILE URL)
    file(DOWNLOAD ${URL} ${CMAKE_BINARY_DIR}/include/${FILE})
    IF(NOT Download_Single_Headerlib_flag)
       include_directories(${CMAKE_BINARY_DIR}/include)
       set(Download_Single_Headerlib_flag TRUE)
    ENDIF()
endmacro()

Download_Single_Headerlib(
    clipp.h
    https://raw.githubusercontent.com/muellan/clipp/v1.2.3/include/clipp.h
    )

#============ Targets settings ==================================#

add_executable( application application.cpp )

File: application.cpp

#include <iostream> 
#include <string> 

#include <clipp.h>

struct ServerOptions
{        
        // --------- Required values -------------------//
        int         port;
        std::string host; 

        // -------- Optional values -------------------//
        int         loglevel                = 1; 
        std::string name                    = "untitled";
        bool        require_authentication  = false;    
        bool        verbose                 = false;         
};

int main(int argc, char* argv []) 
{
        ServerOptions opts; 

        auto cli = (
                // --- Positional values ===>> Required --------------//
                  clipp::value("host name", opts.host)
                , clipp::value("server port", opts.port)

                // --- Optional values ------------------------//
                , clipp::option("-v", "--verbose")
                        .set(opts.verbose)
                        .doc("Enable versbosity")
                , clipp::option("-a", "--require-auth")
                        .set(opts.require_authentication)
                        .doc("Require authentication")                
                , clipp::option("--loglevel") & clipp::value("set server's log level", opts.loglevel)
                , clipp::option("-n", "--name") & clipp::value("server name", opts.name)
                );

        if( !clipp::parse(argc, argv, cli) ) {
                std::cout << clipp::make_man_page(cli, argv[0] );
                return EXIT_SUCCESS;
        }

        std::cout << std::boolalpha; 

        std::cout << " Running server " 
                  << " \n         port = " << opts.port 
                  << " \n         host = " << opts.host 
                  << " \n      verbose = " << opts.verbose
                  << " \n require_auth = " << opts.require_authentication
                  << " \n         name = " << opts.name
                  << " \n   log level  = " << opts.loglevel
                  << "\n";

        return EXIT_SUCCESS;
}

Running

Building:

$ cmake -H. -B_build -DCMAKE_BUILD_TYPE=Debug 
$ cmake --build _build --target 

Running [1]:

 $ ./application 
SYNOPSIS
        ./application <host name> <server port> [-v] [-a] [--loglevel <set server's log level>] [-n
                      <server name>]

OPTIONS
        -v, --verbose
                    Enable versbosity

        -a, --require-auth
                    Require authentication

Running [2]:

$ ./application 127.0.0.1 8080 
Running server  
        port = 8080 
        host = 127.0.0.1 
     verbose = false 
require_auth = false 
        name = untitled 
  log level  = 1

Running [3]:

$ ./application 127.0.0.1 8080 --verbose --name "lisp server"
Running server  
        port = 8080 
        host = 127.0.0.1 
     verbose = true 
require_auth = false 
        name = lisp server 
  log level  = 1

1.7 Serialization

1.7.1 Cereal - Serialization library

Cereal is a lightweight header-only library for serialization which supports a wide variety of serialization formats such as binary, XML and JSON. The library also provides lots of facilities for serializing and deserializing STL containers.

Serialization Challenges

Despite what it may look like, serialization is not easy, there are lots of serialization pitfalls that a library shoudl deal with:

  • Machine endianess:
    • The order that bytes of numeric data are loaded into memory depends on the machine architecture, therefore a numeric data stored in a binary file generated in a machine with a little endian processor, may be read in a wrong way by a big endian machine. So, in order to be portable, a binary file must use a default endianess independent of the any processor.
  • Data Sizes:
    • C and C++ binary sizes of fundamental data types such as int, long and long long are not constant across different operating systems and machine architectures. So a value of long type save in binary format in one machine may be read in a wrong way in a machine where the long type has a different size in bytes. The only way to ensure that the data type is portable and avoid bad surprises is to use fixed width integers from header <stdint> uint32_t, uint64_t and so on.
  • Data Versioning
    • In order to avoid data corruption, a serialization format should support multiple versions of the data structure being serialized and check the version of the data whenever it is deserialized from any stream.

Supported Serialization Formats:

  • XML
  • JSON
  • Binary

Headers:

  • Serialization formats:
    • Binary Format:
      • <cereal/archives/binary.hpp>
    • Portable Binary Format:
      • <cereal/archives/portable_binary.hpp>
    • XML
      • <cereal/archives/xml.hpp>
    • JSON - JavaScript Object Notation
      • <cereal/archives/json.hpp>
  • Support for serializing STL containers
    • <cereal/types/vector.hpp> => STL Vector std::vector serialization
  • Support for polymorphic types serialization
    • <cereal/types/polymorphic.hpp>

Sample Code:

  • File: cereal_test.cpp
#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
#include <fstream> // std::ofstream
#include <sstream> // std::stringstream

#include <cereal/archives/binary.hpp>
// #include <cereal/archives/portable_binary.hpp>
#include <cereal/archives/xml.hpp>
#include <cereal/archives/json.hpp>

// Allows std::vector serialization
#include <cereal/types/vector.hpp>

struct DummyClass
{
    double x;
    float  y;
    size_t N;
    std::vector<int> list;

    DummyClass(): x(0), y(0), N(0){ }

    DummyClass(double x, float y, size_t N)
        : x(x), y(y), N(N){ }

    void Add(int x)
    {
        list.push_back(x);
    }

    template<typename Archive>
    void serialize(Archive& archive)
    {
        // Archive without name-value pair
        //--------------------------------
        // archive(x, y, N, list);

        // Archive with name-value pair
        //-------------------------------
        archive(cereal::make_nvp("x", x),
                cereal::make_nvp("y", y),
                cereal::make_nvp("N", N),
                cereal::make_nvp("list", list)
                );
    }

    void show(std::ostream& os)
    {
        os << "DummyClass { x = " << x
           << " ; y = " << y
           << " ; N = " << N
           << " } => ";
        os << " List[ ";
        for(auto const& e: list)
            os << " " << e;
        os << " ] " << "\n";
    }
};


int main()
{
    // ============ Binary Serialization ==================//

    // Memory "file" simulating a disk file
    auto mock_file = std::stringstream{};

    std::cout << "\n=== Experiment 1 - Serialize object to binary file ===\n";
    {
        auto outArchive   = cereal::BinaryOutputArchive(mock_file);
        DummyClass obj{100.6534, 45.5f, 100};
        obj.Add(100); obj.Add(200); obj.Add(50); obj.Add(80);
        outArchive(obj);
        std::cout << " Content of mock-file =  " << mock_file.str() << std::endl;
    }

    std::cout << "\n=== Experiment 2 - Deserialize object from binary file ====\n" ;
    {
        auto inArchive = cereal::BinaryInputArchive(mock_file);
        DummyClass cls;
        inArchive(cls);
        cls.show(std::cout);
    }

    //============= XML Serialization ============================//

    // auto xmlFile = std::ofstream("/tmp/dataset.xml");
    auto xmlFile = std::stringstream{};
    std::cout << "\n=== Experiment 3 - Serialize object to XML file ====\n" ;
    {
        auto outArchive = cereal::XMLOutputArchive(xmlFile);
        DummyClass obj1{200.0, -802.5f, 900};
        obj1.Add(100); obj1.Add(200); obj1.Add(50); obj1.Add(80);
        DummyClass obj2{400.0, -641.f, 300};
        outArchive(obj1, obj2);
    }
    // Note: Cereal uses RAII to flush the archive output, so the output is only
    // guaranteeed to be written to the stream when the archive go out of escope.
    std::cout << " [TRACE] xmlFile = " << xmlFile.str() << std::endl;

    std::cout << "\n=== Experiment 4 - Deserialize object from XML file ===\n" ;
    {
        auto inArchive = cereal::XMLInputArchive(xmlFile);
        DummyClass obj1, obj2;
        // Read two objects from stream
        inArchive(obj1, obj2);
        obj1.show(std::cout);
        obj2.show(std::cout);
    }

    // ============= JSON Serialization =================================//


    // auto xmlFile = std::ofstream("/tmp/dataset.xml");
    auto jsonFile = std::stringstream{};
    std::cout << "\n=== Experiment 5 - Serialize object to JSON file ====\n" ;
    {
        auto outArchive = cereal::JSONOutputArchive(jsonFile);
        DummyClass obj1{200.0, -802.5f, 900};
        obj1.Add(100); obj1.Add(200); obj1.Add(50); obj1.Add(80);
        DummyClass obj2{400.0, -641.f, 300};
        outArchive(cereal::make_nvp("object1", obj1),
                   cereal::make_nvp("object2", obj2));
    }
    std::cout << " [TRACE] JSON File =\n" << jsonFile.str() << std::endl;

    std::cout << "\n=== Experiment 6 - Deserialize object from JSON file ====\n" ;
    {
        auto inArchive = cereal::JSONInputArchive(jsonFile);
        DummyClass obj1, obj2;
        // Read two objects from stream
        inArchive(obj1, obj2);
        obj1.show(std::cout);
        obj2.show(std::cout);
    }

    return 0;
}

Program output:

$ ./cereal_test.bin

=== Experiment 1 - Serialize object to binary file ===
 Content of mock-file =  6�;N�)Y@��6Bd��������������d�������2���P���

=== Experiment 2 - Deserialize object from binary file ====
DummyClass { x = 100.653 ; y = 45.5 ; N = 100 } =>  List[  100 200 50 80 ]

=== Experiment 3 - Serialize object to XML file ====
 [TRACE] xmlFile = <?xml version="1.0" encoding="utf-8"?>
<cereal>
        <value0>
                <x>200</x>
                <y>-802.5</y>
                <N>900</N>
                <list size="dynamic">
                        <value0>100</value0>
                        <value1>200</value1>
                        <value2>50</value2>
                        <value3>80</value3>
                </list>
        </value0>
        <value1>
                <x>400</x>
                <y>-641</y>
                <N>300</N>
                <list size="dynamic"/>
        </value1>
</cereal>



=== Experiment 4 - Deserialize object from XML file ===
DummyClass { x = 200 ; y = -802.5 ; N = 900 } =>  List[  100 200 50 80 ]
DummyClass { x = 400 ; y = -641 ; N = 300 } =>  List[  ]

=== Experiment 5 - Serialize object to JSON file ====
 [TRACE] JSON File =
{
    "object1": {
        "x": 200.0,
        "y": -802.5,
        "N": 900,
        "list": [
            100,
            200,
            50,
            80
        ]
    },
    "object2": {
        "x": 400.0,
        "y": -641.0,
        "N": 300,
        "list": []
    }
}

=== Experiment 6 - Deserialize object from JSON file ====
DummyClass { x = 200 ; y = -802.5 ; N = 900 } =>  List[  100 200 50 80 ]
DummyClass { x = 400 ; y = -641 ; N = 300 } =>  List[  ]

1.7.2 YAS - Yet Another Serialization Library

YAS - Yet Another Serialization Library - High performance ligthweight header-only serialization library with support for all STL containers.

Repository:

More Examples:

Supported Serialization Formats:

  • binary (portable, endianess-independent)
  • text
  • json (not fully compatible)

Problems:

  • Less documentation
  • Lack of doxygen comments
  • Not comptabile with C++'s standard library streams such as std::ostream, std::fstream, std::cout.

CMake Project Example

  • File: CMakeLists.txt
cmake_minimum_required(VERSION 3.9)
project(YAS_PROJECT)

#========== Global Configurations =============#
#----------------------------------------------#
set(CMAKE_CXX_STANDARD 17)     
set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_CXX_EXTENSIONS OFF)

#------------- Fetch Serialization Library YAS -------------------#

include( FetchContent )
FetchContent_Declare( yas
    GIT_REPOSITORY  "https://github.com/niXman/yas.git"
    GIT_TAG         "7.0.4"
)
FetchContent_MakeAvailable(yas)
include_directories("${yas_SOURCE_DIR}/include/" )

#========== Targets Configurations ============#

add_executable(executable serialize.cpp)
  • File: serialize.cpp
#include <iostream>
#include <deque>
#include <algorithm>
#include <numeric>
#include <initializer_list>
#include <fstream>

#include <yas/serialize.hpp>
#include <yas/std_types.hpp>
#include <yas/text_iarchive.hpp>
#include <yas/text_oarchive.hpp>

/** @brief Read whole input stream to a string and returns it
  * Requires headers: <iostream> and <sstream>
  */
std::string
istreamToString(std::istream& is){
    if(is.bad()){
        throw std::runtime_error("Error: stream has errors.");
    }
    std::stringstream ss;
    ss << is.rdbuf();
    return ss.str();
}

class Stats
{

    std::string m_name = "";
    std::deque<double> m_data = {};
public:
    Stats() { }

    Stats(std::initializer_list<double> const& list)
        : m_data(list.begin(), list.end()) { }

    void   insert(double x){ m_data.push_back(x); }
    double get(size_t index) const { return m_data[index]; }
    size_t size()            const { return m_data.size(); }
    auto begin() { return m_data.begin(); }
    auto end()   { return m_data.end();   }

    std::string name() const { return m_name; }
    void set_name(std::string name) { m_name = name; }

    double mean() const {
        double total = std::accumulate(m_data.begin(), m_data.end(), 0.0);
        return total / m_data.size();
    }

    double first() const { return m_data.front(); }
    double last()  const{ return m_data.back(); }

    /** Required friend function for making class printable */
    friend std::ostream& operator<<(std::ostream& os, const Stats& stat)
    {
        os <<  "Stats { name = '" << stat.m_name << "' ; data = [ ";
        for(auto x: stat.m_data) { os << x << ", "; }
        return os << " ] }";
    }

    /** Required method for making the class serializable */
    template<typename Archive>
    void serialize(Archive& ar)
    {
        // NVP => Name-value-pair
        ar & YAS_OBJECT_NVP(
              "Stats"
            ,("name", m_name)
            ,("data", m_data)
            );
    }

};


int main()
{

    const char* jsonfile = "program_data.json";

    std::cout << "\n ==== EXPERIMENT 1 ====== Save data to file ========\n\n";

    {
        auto stats = Stats{4.5, -10.3, 58.66, 10.6, 9.615, 56.156, 90.51, -62.0};
        stats.set_name("Price change");

        // std::cout << " => Name " << stats.name() << std::endl;
        std::cout << "   => stats = " << stats << std::endl;
        std::cout << "   =>  mean = " << stats.mean() << std::endl;

        // Remove file if it exists
        std::remove(jsonfile);

        // Save archive data in a JSON file
        yas::save<yas::file | yas::json>(jsonfile, stats);
    }

    // Check file content
    auto ifs = std::ifstream{jsonfile};
    auto content = istreamToString(ifs);
    std::cout << "\n File content = \n " << content
              << std::endl;

    std::cout << "\n ==== EXPERIMENT 2 ====== Load data from file ======\n\n";

    {
        auto statsB = Stats{};
        yas::load<yas::file | yas::json>(jsonfile, statsB);
        std::cout << "   => stats = " << statsB << std::endl;
        std::cout << "   =>  mean = " << statsB.mean() << std::endl;
    }

    std::cout << "\n ==== EXPERIMENT 3 ====== Load/Save data from memory ======\n\n";

    // Save to memory
    yas::mem_ostream os;

    auto statsX = Stats{14.5, 25.16, 18.66, -10.6, 62.615, +46.1566, 90.51, 62.61};
    statsX.set_name("Oil prices");
    {
        auto archive = yas::binary_oarchive<yas::mem_ostream>(os);
        archive(statsX);
        std::cout << " StatsX = " << statsX << std::endl;
        std::cout << " [TRACE] Saved to memory OK." << std::endl;
    }

    {
        auto is = yas::mem_istream(os.get_intrusive_buffer());
        auto archive = yas::binary_iarchive<yas::mem_istream>(is);
        auto statsY = Stats();
        archive(statsY);
        std::cout << " [TRACE] Load from memory OK." << std::endl;
        std::cout << " StatsY = " << statsY << std::endl;
    }

    return 0;
}

  • Program output:
==== EXPERIMENT 1 ====== Save data to file ========

  => stats = Stats { name = 'Price change' ; data = [ 4.5, -10.3, 58.66, 10.6, 9.615, 56.156, 90.51, -62,  ] }
  =>  mean = 19.7176

File content = 
{"name":"Price change","data":[4.5,-10.3,58.66,10.6,9.615,56.156,90.51,-62.0]}

==== EXPERIMENT 2 ====== Load data from file ======

  => stats = Stats { name = 'Price change' ; data = [ 4.5, -10.3, 58.66, 10.6, 9.615, 56.156, 90.51, -62,  ] }
  =>  mean = 19.7176

==== EXPERIMENT 3 ====== Load/Save data from memory ======

StatsX = Stats { name = 'Oil prices' ; data = [ 14.5, 25.16, 18.66, -10.6, 62.615, 46.1566, 90.51, 62.61,  ] }
[TRACE] Saved to memory OK.
[TRACE] Load from memory OK.
StatsY = Stats { name = 'Oil prices' ; data = [ 14.5, 25.16, 18.66, -10.6, 62.615, 46.1566, 90.51, 62.61,  ] }

1.8 Parsers

1.8.1 TinyXML2 - Lightweight XML parser

Simple and ligtweight C++ library for parsing XML files.

Site:

Repository:

Conan Reference:

Conan package repository:

Example:

Full Project Code:

File: tinyxml2-test.cpp

  • This file parses the XML taken from eurofxref-daily.xml which contains a set of FX exchange rates.
#include <iostream>
#include <iomanip>

#include <tinyxml2.h>

#define ENABLE_ASSERT

#ifdef ENABLE_ASSERT
#define M_ASSERT(expr) \
      { \
         if(!(expr)){ \
            std::cerr << "ASSERTION FAILURE: \n"; \
            std::cerr << " => Condition: " << #expr << "\n"; \
            std::cerr << " =>  Function: " << __FUNCTION__ << "\n"; \
            std::cerr << __FILE__ << ":" << __LINE__ << ":" << "\n"; \
            std::terminate(); \
         } \
      }
#else
#define M_ASSERT(expr)
#endif

using tinyxml2::XMLText;
using tinyxml2::XMLElement;
using tinyxml2::XMLNode;

extern const char* exchangeRatesXML;

int main()
{

    std::cout << " [INFO] Running TinyXMl2 " << std::endl;

    tinyxml2::XMLDocument doc;

    if(doc.Parse( exchangeRatesXML) != tinyxml2::XML_SUCCESS)
    {
        std::cout << " [ERROR] Failed to parse XML" << std::endl;
        return EXIT_FAILURE;
    }
    std::cout << " [OK] XML parsed successfully" << std::endl;


    tinyxml2::XMLPrinter printer;
    doc.Print(&printer);

    std::cout << "Value: doc.FirstChild()->Value() = " << doc.FirstChild()->Value() << std::endl;

    XMLElement* elem = doc.FirstChildElement("gesmes:Envelope");
    M_ASSERT(elem != nullptr);
    if(elem){
        std::cout << " Element found. OK " << std::endl;
        std::cout << " =>> Element Name = " << elem->Name() << std::endl;

    }

    XMLElement* elem1 = elem->FirstChildElement("Cube");
    M_ASSERT(elem1 != nullptr);

    std::cout << " =>> Found Node Name: " << elem1->ToElement()->Name() << "\n";

    XMLElement* elem2 = elem1->FirstChildElement("Cube");
    M_ASSERT(elem2 != nullptr);

    const char* time = elem2->Attribute("time");
    M_ASSERT(time != nullptr);

    // XML node with: <Cube time = 'xxxx-xx-xx'>
    std::cout << " => Time = " << time << "\n\n";
    std::cout << std::fixed << std::setprecision(3);

    std::cout << " ===== Exchange rates per Euro ====" << std::endl;

    for(XMLElement* e = elem2->FirstChildElement("Cube")
             ; e != nullptr; e = e->NextSiblingElement("Cube") )
    {
        std::cout << std::setw(10) << e->Attribute("currency")
                  << std::setw(15) << std::stod(e->Attribute("rate"))
                  << std::endl;
    }


    return doc.ErrorID();
}

// Source: https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml
const char* exchangeRatesXML = R"(
   <?xml version="1.0" encoding="UTF-8"?>
   <gesmes:Envelope 
                xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01" 
                xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">

           <gesmes:subject>Reference rates</gesmes:subject>
           <gesmes:Sender>
                   <gesmes:name>European Central Bank</gesmes:name>
           </gesmes:Sender>
           <Cube>
                   <Cube time='2019-06-14'>
                           <Cube currency='USD' rate='1.1265'/>
                           <Cube currency='JPY' rate='121.90'/>
                           <Cube currency='BGN' rate='1.9558'/>
                           <Cube currency='CZK' rate='25.540'/>
                           <Cube currency='DKK' rate='7.4676'/>
                           <Cube currency='GBP' rate='0.89093'/>
                           <Cube currency='HUF' rate='321.53'/>
                           <Cube currency='PLN' rate='4.2534'/>
                           <Cube currency='RON' rate='4.7233'/>
                           <Cube currency='SEK' rate='10.6390'/>
                           <Cube currency='CHF' rate='1.1211'/>
                           <Cube currency='ISK' rate='141.50'/>
                           <Cube currency='NOK' rate='9.7728'/>
                           <Cube currency='HRK' rate='7.4105'/>
                           <Cube currency='RUB' rate='72.3880'/>
                           <Cube currency='TRY' rate='6.6427'/>
                           <Cube currency='AUD' rate='1.6324'/>
                           <Cube currency='BRL' rate='4.3423'/>
                           <Cube currency='CAD' rate='1.5018'/>
                           <Cube currency='CNY' rate='7.7997'/>
                           <Cube currency='HKD' rate='8.8170'/>
                           <Cube currency='IDR' rate='16128.10'/>
                           <Cube currency='ILS' rate='4.0518'/>
                           <Cube currency='INR' rate='78.6080'/>
                           <Cube currency='KRW' rate='1333.60'/>
                           <Cube currency='MXN' rate='21.6073'/>
                           <Cube currency='MYR' rate='4.6981'/>
                           <Cube currency='NZD' rate='1.7241'/>
                           <Cube currency='PHP' rate='58.539'/>
                           <Cube currency='SGD' rate='1.5403'/>
                           <Cube currency='THB' rate='35.101'/>
                           <Cube currency='ZAR' rate='16.6529'/>
                   </Cube>
           </Cube>
   </gesmes:Envelope>
)";

File: CMakeLists.txt

cmake_minimum_required(VERSION 3.9)
project(tinyxml2-test)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_VERBOSE_MAKEFILE ON)

#=========== Conan Bootstrap =================#

# Download automatically, you can also just copy the conan.cmake file
if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake")
   message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan")
   file(DOWNLOAD "https://github.com/conan-io/cmake-conan/raw/v0.13/conan.cmake"
                 "${CMAKE_BINARY_DIR}/conan.cmake")
endif()

include(${CMAKE_BINARY_DIR}/conan.cmake)
set(CONAN_PROFILE default)

conan_cmake_run( REQUIRES
                 tinyxml2/7.0.1@nicolastagliani/stable
                 BASIC_SETUP
                 BUILD missing )

#=========== Find Package ================#

include(tinyxml2_helper.cmake)

#=========== Targets =====================# 

add_executable(tinyxml2-test tinyxml2-test.cpp)
target_link_libraries(tinyxml2-test PRIVATE ${tinyxml2_LIBRARY})

File: tinyxml2_helper.cmake

# Credits: https://github.com/nicolastagliani/conan-tinyxml2/issues/3
include( FindPackageHandleStandardArgs )

find_path( tinyxml2_INCLUDE_DIR
  NAMES
    tinyxml2.h
  PATHS
    ${CONAN_INCLUDE_DIRS_TINYXML2}
)

find_library( tinyxml2_LIBRARY
  NAMES
    ${CONAN_LIBS_TINYXML2}
  PATHS
    ${CONAN_LIB_DIRS_TINYXML2}
)

find_package_handle_standard_args( tinyxml2 DEFAULT_MSG
  tinyxml2_INCLUDE_DIR
)

if( tinyxml2_FOUND )
  set( tinyxml2_INCLUDE_DIRS ${tinyxml2_INCLUDE_DIR} )
  set( tinyxml2_LIBRARIES ${tinyxml2_LIBRARY} )

  get_filename_component( tinyxml2_CONFIG_PATH ${CONAN_TINYXML2_ROOT} DIRECTORY )
  get_filename_component( tinyxml2_HASH ${CONAN_TINYXML2_ROOT} NAME )
  get_filename_component( tinyxml2_CONFIG_PATH ${tinyxml2_CONFIG_PATH} DIRECTORY )
  set( tinyxml2_CONFIG_PATH  ${tinyxml2_CONFIG_PATH}/build/${tinyxml2_HASH} )
  set( tinyxml2_CONFIG_FILENAME tinyxml2Config.cmake )

  find_file( tinyxml2_CONFIG_DIR
      ${tinyxml2_CONFIG_FILENAME}
    HINTS
      ${tinyxml2_CONFIG_PATH}
  )

  if( tinyxml2_CONFIG_DIR-NOTFOUND )
    set( tinyxml2_CONFIG "" )
  else()
    set( tinyxml2_CONFIG ${tinyxml2_CONFIG_DIR} )
  endif()

  mark_as_advanced(
    tinyxml2_INCLUDE_DIR
    tinyxml2_LIBRARY
    tinyxml2_DIR
    tinyxml2_CONFIG
  )
else()
  set( tinyxml2_DIR "" CACHE STRING
    "An optional hint to a tinyxml2 directory"
  )
endif()

Build

$ git clone https://gist.github.com/caiorss/351e291b8df2b0fc8e1bba5c86b7ee4d gist 
$ cd gist 

# Build with QT Creator 
$ qtcreator CMakeLists.txt 

# Build from command line 
$ cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug
$ cmake --build build --target

Program Output:

  • Meaning: USD 1.127 => Means that 1 Euro = 1.127 USD or that the exchange rate is 1.127 per Euro.
$ build/bin/tinyxml2-test 

 [INFO] Running TinyXMl2 
 [OK] XML parsed successfully
Value: doc.FirstChild()->Value() = xml version="1.0" encoding="UTF-8"
 Element found. OK 
 =>> Element Name = gesmes:Envelope
 =>> Found Node Name: Cube
 => Time = 2019-06-14

 ===== Exchange rates per Euros ====
       USD          1.127
       JPY        121.900
       BGN          1.956
       CZK         25.540
       DKK          7.468
       GBP          0.891
       HUF        321.530
       PLN          4.253
       RON          4.723
       SEK         10.639
       CHF          1.121
       ISK        141.500
       NOK          9.773
       HRK          7.410
       RUB         72.388
       TRY          6.643
       AUD          1.632
       BRL          4.342
       CAD          1.502
       CNY          7.800
       HKD          8.817
       IDR      16128.100
       ILS          4.052
       INR         78.608
       KRW       1333.600
       MXN         21.607
       MYR          4.698
       NZD          1.724
       PHP         58.539
       SGD          1.540
       THB         35.101
       ZAR         16.653

1.8.2 PugiXML - Lightweight XML parser

PugiXML is lightweight XML parsing library with DOM (Document Object Model) transversing and XPATH capabilities.

Official Web Site:

Documentation:

Repository:

Conan Reference:

Example:

File: pugixml_test1.cpp

#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>

#include <pugixml.hpp>

extern const char* exchangeRatesXML;

int main()
{
    // This input stream 'is' can be replaced by
    // any other input stream without any code modification
    // such as:  std::ifstream is("/tmp/input-file.xml")
    std::stringstream is{exchangeRatesXML};

    pugi::xml_document     doc;
    pugi::xml_parse_result result = doc.load(is);

    if(!result){
        std::cerr << " [ERROR] Failed to parse the XML. Invalid file." << std::endl;
        std::exit(EXIT_FAILURE);
    }

    std::string sender_name = doc.child("gesmes:Envelope")
                                  .child("gesmes:Sender")
                                  .child_value("gesmes:name");

    // type: const char*
    auto subject = doc.child("gesmes:Envelope")
                      .child_value("gesmes:subject");

    auto time = doc.child("gesmes:Envelope")
                   .child("Cube")
                   .child("Cube").attribute("time")
                   .value();

    std::cout << " ========= DOCUMENT INFO ================" << std::endl;
    std::cout << " => Sender name = " << sender_name << std::endl;
    std::cout << " =>     Subject = " << subject << std::endl;
    std::cout << " =>        Time = " << time << std::endl;

    auto parent = doc.child("gesmes:Envelope")
                              .child("Cube")
                              .child("Cube");

    std::cout << std::fixed << std::setprecision(3);

    std::cout <<"\n Exchange Rates per EURO " << std::endl;

    for(auto const& node : parent)
    {
        std::cout << std::setw(10) << node.attribute("currency").value()
                  << std::setw(10) << std::stod(node.attribute("rate").value())
                  << std::endl;
    }

    std::cout << "\n\n *********** Extracting Data with XPATH ********\n\n";

    pugi::xpath_node sender_name2 = doc.select_node("//gesmes:name");
    std::cout << " Sender = " << sender_name2.node().child_value() << std::endl;

    auto time2 = doc.select_node("//Cube[@time]");
    std::cout << " Time = " << time2.node().attribute("time").value() << std::endl;

    std::cout <<"\n Exchange Rates per EURO - Extracted with XPATH " << std::endl;

    // Type: pugi::xpath_node_set
    auto dataNodes = doc.select_nodes("/gesmes:Envelope/Cube/Cube/Cube");

    for(auto const& n : dataNodes)
    {
        std::cout << std::setw(10) << n.node().attribute("currency").value()
                  << std::setw(10) << std::stod(n.node().attribute("rate").value())
                  << std::endl;
    }


    return 0;
}


// Source: https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml
const char* exchangeRatesXML = R"(
   <?xml version="1.0" encoding="UTF-8"?>
   <gesmes:Envelope
                xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01"
                xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">

       <gesmes:subject>Reference rates</gesmes:subject>
       <gesmes:Sender>
           <gesmes:name>European Central Bank</gesmes:name>
       </gesmes:Sender>
       <Cube>
           <Cube time='2019-06-14'>
               <Cube currency='USD' rate='1.1265'/>
               <Cube currency='JPY' rate='121.90'/>
               <Cube currency='BGN' rate='1.9558'/>
               <Cube currency='CZK' rate='25.540'/>
               <Cube currency='DKK' rate='7.4676'/>
               <Cube currency='GBP' rate='0.89093'/>
               <Cube currency='HUF' rate='321.53'/>
               <Cube currency='PLN' rate='4.2534'/>
               <Cube currency='RON' rate='4.7233'/>
               <Cube currency='SEK' rate='10.6390'/>
               <Cube currency='CHF' rate='1.1211'/>
               <Cube currency='ISK' rate='141.50'/>
               <Cube currency='NOK' rate='9.7728'/>
               <Cube currency='HRK' rate='7.4105'/>
           </Cube>
       </Cube>
   </gesmes:Envelope>
)";

File: CMakeLists.txt

cmake_minimum_required(VERSION 3.9)
project(purgixml)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_VERBOSE_MAKEFILE ON)

# ============= Conan Boosttrap =========================#

# Download automatically, you can also just copy the conan.cmake file
if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake")
   message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan")
   file(DOWNLOAD "https://github.com/conan-io/cmake-conan/raw/v0.13/conan.cmake"
                 "${CMAKE_BINARY_DIR}/conan.cmake")
endif()

include(${CMAKE_BINARY_DIR}/conan.cmake)

# Possible values "default" and "llvm8"
set(CONAN_PROFILE default)

conan_cmake_run(REQUIRES
                  pugixml/1.9@bincrafters/stable
                BASIC_SETUP
                BUILD missing)

# ============= Find Package ===========================#

find_package(pugixml REQUIRED)

#============= Targets Settings ========================#

add_executable(pugixml_test1 pugixml_test1.cpp)
target_link_libraries(pugixml_test1 pugixml)

Program output:

========= DOCUMENT INFO ================
 => Sender name = European Central Bank
 =>     Subject = Reference rates
 =>        Time = 2019-06-14

 Exchange Rates per EURO 
       USD     1.127
       JPY   121.900
       BGN     1.956
       CZK    25.540
       DKK     7.468
       GBP     0.891
       HUF   321.530
       PLN     4.253
       RON     4.723
       SEK    10.639
       CHF     1.121
       ISK   141.500
       NOK     9.773
       HRK     7.410


 *********** Extracting Data with XPATH ********

 Sender = European Central Bank
 Time = 2019-06-14

 Exchange Rates per EURO - Extracted with XPATH 
       USD     1.127
       JPY   121.900
       BGN     1.956
       CZK    25.540
       DKK     7.468
       GBP     0.891
       HUF   321.530
       PLN     4.253
       RON     4.723
       SEK    10.639
       CHF     1.121
       ISK   141.500
       NOK     9.773
       HRK     7.410

1.8.3 nlohmann JSON Parser Library

Easy to use single-file header-only library for parsing and writing JSON (Javascript Object Notation).

Example:

  • File: CMakeLists.txt
    • Version with Conan
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
project(cppjson)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_VERBOSE_MAKEFILE ON)

# ============= Conan Bootstrap =============================#

# Download automatically, you can also just copy the conan.cmake file
if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake")
   message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan")
   file(DOWNLOAD "https://github.com/conan-io/cmake-conan/raw/v0.13/conan.cmake"
                 "${CMAKE_BINARY_DIR}/conan.cmake")
endif()

include(${CMAKE_BINARY_DIR}/conan.cmake)

conan_cmake_run(REQUIRES     
                             jsonformoderncpp/3.7.0@vthiery/stable
                BASIC_SETUP
                BUILD missing
                )
#===== Targets ===========================#

# Conan package: jsonformoderncpp/3.7.0@vthiery/stable
find_package(nlohmann_json 3.2.0 REQUIRED)
add_executable(json-parse  json-parse.cpp)
target_link_libraries(json-parse nlohmann_json::nlohmann_json)
  • File: CMakeLists.txt
    • Version without Conan with custom macro
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
project(cmake-experiment)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_VERBOSE_MAKEFILE ON)

macro(Download_Single_Headerlib_dir FILE PATH URL)
    file(DOWNLOAD ${URL} ${CMAKE_BINARY_DIR}/include/${PATH}${FILE})
    IF(NOT Download_Single_Headerlib_flag)
       include_directories(${CMAKE_BINARY_DIR}/include)
       set(Download_Single_Headerlib_flag TRUE)
    ENDIF()
endmacro()

# #include <nlohmann/json.hpp>
Download_Single_Headerlib_dir(
     json.hpp "nlohmann/"
    "https://github.com/nlohmann/json/raw/develop/single_include/nlohmann/json.hpp"
)

# --------------------------------------------#

add_executable(json-parse json-parse.cpp)
  • File: json-parse.cpp
#include <iostream>
#include <sstream>

#include <nlohmann/json.hpp>

using json = nlohmann::json;

int main()
{
    std::puts("\n====== EXPERIMETN 1 - Create JSON Object ===========================");

    {
        json top;
        top["hero1"] = "John Von Neumman / Modern Computers";
        top["hero2"] = "Dennis Ritchie / C Language";
        top["points"] = 200;
        top["list_number"]  = {1, 2, 3, 3, 4, 5, 6};
        top["list_string"]  = {"Rice", "Coffee", "Beans", "Milk"};

        top["currency_basket"] = {{"USD", 10}, {"BRL", 2000}, {"CAD", 90}, {"AUD", "N/A"}};

        json cd;
        cd["latitude"]  = -23.5505;
        cd["longitude"] = -46.6333;
        cd["location"]  = "São Paulo city";

        top["coordiante"] = cd;

        std::cout << "\n VALUE OF JSON [NOT PRETTY PRINT] = "
                  << top << std::endl;

        std::cout << "\n VALUE OF JSON [PRETTY PRINT] = "
                  << top.dump(4) << std::endl;

    }

     std::puts("\n====== EXPERIMETN 2 - Deserialization from string literal ===========");

     {
         json d;

         try {
             // Parsing from string literal
             d = R"(
                      {
                        "x"            : 1956.2322
                      , "y"            : 1000
                      , "name"        : "Dennis Ritchie"
                      , "fruits" : [ "orange", "jabuticaba", "banana", "apple"  ]
                      , "basket" : { "BONDS": 1235, "CDS": 200.354, "FX": 500
                                     , "market": "Emergin Markets"}
                     }
               )"_json;
         } catch(nlohmann::detail::parse_error& ex)
         {
             std::cout << " [ERROR] I cannot parse it: " << ex.what() << std::endl;
         }

         std::cout << "\n VALUE OF JSON PRETTY PRINT = " << d.dump(3) << std::endl;

         std::cout << " [RESULT]    x = " << static_cast<double>(d["x"]) << std::endl;
         std::cout << " [RESULT]    x = " << d["x"].get<double>() << std::endl;
         std::cout << " [RESULT]    y = " << d["y"].get<int>()    << std::endl;
         std::cout << " [RESULT] name = " << d["name"].get<std::string>() << std::endl;

         std::cout << " [RESULT] fruit[0] = " << d["fruits"][0] << std::endl;
         std::cout << " [RESULT] fruit[1] = " << d["fruits"][1] << std::endl;
         std::cout << " [RESULT] fruit[2] = " << d["fruits"][2].get<std::string>() << std::endl;

         std::cout << " [RESULT]  d['basket']['BONDS'] = "
                   << d["basket"]["BONDS"].get<int>()
                   << std::endl;

         std::cout << " [RESULT]  d['basket']['CDS'] = "
                   << d["basket"]["CDS"].get<double>()
                   << std::endl;

         std::cout << " [RESULT]  d['basket']['market'] = "
                   << d["basket"]["market"].get<std::string>()
                   << std::endl;

         // Attempt to generate a parser error
         try
         {
             // Note: Expects failure, the exception will be thrown!
             double value = static_cast<double>(d["name"]);
             std::cout << " [RESULT]  2 *  x = " <<  2 * value
                       << "\n\n";
         } catch (nlohmann::detail::type_error& ex)
         {
             std::cout << " [ERROR] err = " << ex.what() << std::endl;
         }


     }

     std::puts("\n====== EXPERIMETN 3 - Deserialization from stream ===========");
     {
         const char* json_text = R"(
                      {
                        "x"            : 1956.2322
                      , "y"            : 1000
                      , "flag"         : true
                      , "name"         : "Dennis Ritchie"
                      , "asset_basket" : { "CAD": 100, "BRL": 200, "USD": 500}
                     }
               )";

         json js;
         /* Input stream could be a file:
          *     auto is = std::ifstream("/tmp/file");
          *------------------------------------------*/
         auto is = std::stringstream(json_text);
         is >> js; // Extract from memory stream

         std::cout << std::boolalpha;
         std::cout << " JSON VALUE = " << js.dump(4) << std::endl;

         std::cout << " =>      is_null(x) = " << js["x"].is_null()    << std::endl;
         std::cout << " =>   is_boolean(x) = " << js["x"].is_boolean() << std::endl;
         std::cout << " =>    is_number(x) = " << js["x"].is_number()  << std::endl;
         std::cout << " => is_string(name) = " << js["name"].is_string()  << std::endl;

         std::cout << " =>    is_string(asset_basket) = "
                   << js["asset_basket"].is_string() << std::endl;

         std::cout << " =>    is_object(asset_basket) = "
                   << js["asset_basket"].is_object()  << std::endl;

         std::puts("\n  >>>--|>>> Iterate Over JSON AST - Abstract Syntax Tree <<<|---<<< ");

         for(auto const& element: js)
             std::cout << " => element = " << element << std::endl;

     }
}

Output:

====== EXPERIMETN 1 - Create JSON Object ===========================

 VALUE OF JSON [NOT PRETTY PRINT] = {"coordiante":{"latitude":-23.5505,"location":"São Paulo city","longitude":-46.6333}, ....}

 VALUE OF JSON [PRETTY PRINT] = {
    "coordiante": {
        "latitude": -23.5505,
        "location": "São Paulo city",
        "longitude": -46.6333
    },
    "currency_basket": {
        "AUD": "N/A",
        "BRL": 2000,
        "CAD": 90,
        "USD": 10
    },
    "hero1": "John Von Neumman / Modern Computers",
    "hero2": "Dennis Ritchie / C Language",
    "list_number": [
        1,
        2,
        3,
        3,
        4,
        5,
        6
    ],
    "list_string": [
        "Rice",
        "Coffee",
        "Beans",
        "Milk"
    ],
    "points": 200
}

====== EXPERIMETN 2 - Deserialization from string literal ===========

 VALUE OF JSON PRETTY PRINT = {
   "basket": {
      "BONDS": 1235,
      "CDS": 200.354,
      "FX": 500,
      "market": "Emergin Markets"
   },
   "fruits": [
      "orange",
      "jabuticaba",
      "banana",
      "apple"
   ],
   "name": "Dennis Ritchie",
   "x": 1956.2322,
   "y": 1000
}
 [RESULT]    x = 1956.23
 [RESULT]    x = 1956.23
 [RESULT]    y = 1000
 [RESULT] name = Dennis Ritchie
 [RESULT] fruit[0] = "orange"
 [RESULT] fruit[1] = "jabuticaba"
 [RESULT] fruit[2] = banana
 [RESULT]  d['basket']['BONDS'] = 1235
 [RESULT]  d['basket']['CDS'] = 200.354
 [RESULT]  d['basket']['market'] = Emergin Markets
 [ERROR] err = [json.exception.type_error.302] type must be number, but is string

====== EXPERIMETN 3 - Deserialization from stream ===========
 JSON VALUE = {
    "asset_basket": {
        "BRL": 200,
        "CAD": 100,
        "USD": 500
    },
    "flag": true,
    "name": "Dennis Ritchie",
    "x": 1956.2322,
    "y": 1000
}
 =>      is_null(x) = false
 =>   is_boolean(x) = false
 =>    is_number(x) = true
 => is_string(name) = true
 =>    is_string(asset_basket) = false
 =>    is_object(asset_basket) = true

  >>>--|>>> Iterate Over JSON AST - Abstract Syntax Tree <<<|---<<< 
 => element = {"BRL":200,"CAD":100,"USD":500}
 => element = true
 => element = "Dennis Ritchie"
 => element = 1956.2322
 => element = 1000

1.8.4 cpptoml configuration parser

cpptoml is a single-file header-only library for parsing TOML configuration files that resembles the Windows INI files and are more lightweight than XML and JSON.

Repository:

Download the library:

$ cd <PROJECT_DIR> 
$ curl -O -L https://raw.githubusercontent.com/skystrife/cpptoml/v0.1.1/include/cpptoml.h

File: main.cpp

#include <iostream>
#include <string>
#include <sstream>

#include <cpptoml.h>

template <typename T>
using sh = std::shared_ptr<T>;

extern const char* tomlData;


void parseConfiguration(std::istream& is)
{
    cpptoml::parser p{is};
    sh<cpptoml::table> config = p.parse();

    std::cout << "\n --- TOML Configuration data read from input stream ------\n";

    int loglevel  = config->get_qualified_as<int>("INFO.loglevel").value_or(0);
    auto userName = config->get_qualified_as<std::string>("INFO.user").value_or("unnamed");
    auto file     = config->get_qualified_as<std::string>("INFO.file").value_or("");
    auto port     = config->get_qualified_as<int>("SERVER.port").value_or(8080);
    std::cout << " => loglevel = " << loglevel << "\n"
              << " => userName = " << userName << "\n"
              << " => file     = " << file << "\n"
              << " => port     = " << port << "\n";

    auto locations = config->get_qualified_array_of<std::string>("SERVER.directories");
    if(!locations)
    {
        std::cerr << " [ERROR] SERVER.directories not found." << std::endl;
    }
    for(auto const& path: *locations)
    {
        std::cout << " path: " << path << std::endl;
    }

}

int main(int argc, char** argv)
{
    if(argc == 1){
        std::cerr << " Error: invalid option " << std::endl;
        return EXIT_FAILURE;
    }

    std::string cmd = argv[1];
    if(cmd == "-stream")
    {
        auto is = std::stringstream(tomlData);
        parseConfiguration(is);

        return EXIT_SUCCESS;
    }

    if(cmd == "-file"  && argc == 3)
    {
        auto is = std::ifstream(argv[2]);
        if(!is){
            std::cerr << "Error: file not found." << std::endl;
            std::exit(EXIT_FAILURE);
        }
        parseConfiguration(is);
        return EXIT_SUCCESS;
    }

    std::cerr << " Error: invalid option. " << std::endl;

    return 0;
}

const char* tomlData = R"(
[INFO]
 loglevel = 10
 user     = "Dummy user"
 file     = "C:\\Users\\somebody\\storage\\data.log"

[SERVER]
  host = "127.0.0.1"
  port = 9090
  directories = [
      "C:\\Users\\somebody\\Document"
     ,"C:\\Users\\somebody\\Upload"
     ,"C:\\Users\\somebody\\Pictures"
  ]

)";

File: server.conf

[INFO]
 loglevel = 0 
 user     = "Admin User"
 file     = "C:\\Users\\admin\\files\\log.txt"

[SERVER]
  host = "0.0.0.0"
  port = 9060
  directories = [
      "/Users/admin/Desktop"
      ,"/Applications"
      ,"/Frameworks"
      ,"/tmp"
  ]

Building:

$ g++ main.cpp -o main.bin -std=c++1z -g -O0 -Wall -Wextra -pedantic

# Mesaure compile-time 
$ time g++ main.cpp -o main.bin -std=c++1z -g -O0 -Wall -Wextra -pedantic

real    0m2.462s
user    0m2.262s
sys     0m0.172s

Parse internal data (std::istream):

$ ./main.bin -stream

 --- TOML Configuration data read from input stream ------
 => loglevel = 10
 => userName = Dummy user
 => file     = C:\Users\somebody\storage\data.log
 => port     = 9090
 path: C:\Users\somebody\Document
 path: C:\Users\somebody\Upload
 path: C:\Users\somebody\Pictures

Parse file (std::ifstream):

$ ./main.bin -file server.conf

 --- TOML Configuration data read from input stream ------
 => loglevel = 0
 => userName = Admin User
 => file     = C:\Users\admin\files\log.txt
 => port     = 9060
 path: /Users/admin/Desktop
 path: /Applications
 path: /Frameworks
 path: /tmp

1.9 Unit Testing

1.9.1 GTest - Unit Testing Framework

GTest or Google test is one of the most used and most popular test frameworks which supports many features such as mocks; test fixtures; parameterized tests; XML test report in JUnit / xUnit format; test discovery and integration with IDEs, namely Eclipse, QTCreator and Visual Studio.

Official Web site:

Some Tutorial/Primers:

Conan Reference:

Test Runner for GTest:

CMake Integration:

Fatal Assertions:

  • ASSERT_TRUE
  • ASSERT_FALSE
  • ASSERT_EQ(val1, val2)
  • ASSERT_NE(val1, val2)
  • ASSERT_LT
  • ASSERT_GT
  • ASSERT_GE

Non-fatal assertions Macros:

  • EXPECT_TRUE
  • EXPECT_FALSE
  • EXPECT_EQ
  • EXPECT_NQ
  • EXPECT_DOUBLE_EQ
  • EXPECT_FLOAT_EQ
  • EXPECT_NEAR(value1, value2, absolute_tolerance)
  • TEST(<NAME>, ){ … <BODY> .. }
  • EXPECT_FLOAT_EQ

Example: Usage with CMake and QTCreator

Get the source code:

$ git clone https://gist.github.com/caiorss/eb2fc15b7ed322ccac5ee496585e54e9 gist && cd gist
Cloning into 'gist'...
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (4/4), done.

$ ls
CMakeLists.txt  gtest-experiment.cpp

# Open with QT Creator, Visual Studio or build from command line 
$ qtcreator CMakeList.txt 

# Open with Visual Studio 
$ devenv CMakeLists.txt 

File: gtest-experiment.cpp

Headers:

#include <iostream>
#include <string>
#include <vector>
#include <cassert>

#include <gtest/gtest.h>

Implementations:

// ==================== Implementation ========================

int factorial(int n){
    int prod = 1;
    for(int i = 1; i <= n; i++)
        prod *= i;
    return prod;
}

struct Date
{
    int year;
    int month;
    int day;
    Date(){}
    Date(int year, int month, int day)
        : year(year)
        , month(month)
        , day(day)
    { }

    // Comparison operator required by  EXPECT_EQ
    bool operator==(Date const& rhs) const
    {
        return year  == rhs.year
            && month == rhs.month
            && day   == rhs.day;
    }
    // Necessary for make class printable in GTest
    friend std::ostream& operator<<(std::ostream& os, Date const& rhs)
    {
        return os << "Date { " << rhs.year << " ; "
                  << rhs.month << " ; "
                  << rhs.day << " } ";
    }
};

Date GregorianEasterSunday(int y)
{
    int c = y / 100;
    int n = y - 19 * ( y / 19 );
    int k = ( c - 17 ) / 25;
    int i = c - c / 4 - ( c - k ) / 3 + 19 * n + 15;
    i = i - 30 * ( i / 30 );
    i = i - ( i / 28 ) * ( 1 - ( i / 28 )
          * ( 29 / ( i + 1 ) )
          * ( ( 21 - n ) / 11 ) );
    int j = y + y / 4 + i + 2 - c + c / 4;
    j = j - 7 * ( j / 7 );
    int l = i - j;
    int m = 3 + ( l + 40 ) / 44;
    int d = l + 28 - 31 * ( m / 4 );
    return Date(y, m, d);
}

Test code:

//=============== Tests ====================================//

TEST(FactorialTest, test1){
    EXPECT_EQ(6,        factorial(3));
    EXPECT_EQ(24,       factorial(4));
    EXPECT_EQ(120,      factorial(5));
    EXPECT_EQ(3628800,  factorial(10));
    // Expect greater than
    EXPECT_GT(10000000, factorial(10));
    // Expect not equal
    EXPECT_NE(25, factorial(4));
}

TEST(FactorialTestFailure, testFailure){
    // Deliberately fails for demonstration purposes
    EXPECT_EQ(6, factorial(3));
    EXPECT_EQ(4, factorial(4));
    EXPECT_EQ(6, factorial(2));
}

TEST(GregorianEaster, testdates){
    EXPECT_EQ(Date(2005, 3, 27), GregorianEasterSunday(2005));
    EXPECT_EQ(Date(2008, 3, 23), GregorianEasterSunday(2008));
    EXPECT_EQ(Date(2010, 4, 4),  GregorianEasterSunday(2010));
}

File: CMakeLists.txt

cmake_minimum_required(VERSION 3.9)
project( GtestsExperiment
         VERSION      0.1
         DESCRIPTION  "Experiment with Gtest uni testing framework"
        )
# ============= Conan Bootstrap =============================#

# Download automatically, you can also just copy the conan.cmake file
if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake")
   message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan")
   file(DOWNLOAD "https://github.com/conan-io/cmake-conan/raw/v0.13/conan.cmake"
                 "${CMAKE_BINARY_DIR}/conan.cmake")
endif()

include(${CMAKE_BINARY_DIR}/conan.cmake)

conan_cmake_run(REQUIRES
                gtest/1.8.1@bincrafters/stable
                BASIC_SETUP
                BUILD missing
                )

#============= Find Packages ================================#

find_package(GTest REQUIRED)

#---------------------------------------------------#
#             Targets Settings                      #
#---------------------------------------------------#

add_executable(gtest-experiment gtest-experiment.cpp)
target_link_libraries(gtest-experiment GTest::GTest GTest::Main)
add_test(GTestExperiment gtest-experiment)

Test Output:

gtest-framework-console.png

Figure 1: Test output in Command Line

gtest-framework-qtcreator.png

Figure 2: QTCreator Test Discovery

1.9.2 Catch2 - Unit Testing Framework

Advatanges over other test frameworks:

  • Header-only library, so it requires no linking step or linking dependencies.
  • Uses C++ operators instead of many macros such as (X > 20) instead of EXPECT_GT(X, 20) (expect grater than) as in GTest (Google's test)
  • BDD - Behavior Driven Development
  • Property-based testing - testing with several random values and combination of random values.
  • Test with multiple data or data table which frees the user from writing lots boilerplate ASSERT_EQUAL(x, y). A single REQUIRE macro can be used for test a whole list of cartesian pairs such as (x, y,z, expected)
  • XML output in JUnit, xUnit format.

Problem:

  • Compile-time can be a bit slow due to the library be header-only.

Repository:

Conan Reference:

Code Examples:

Tutorial:

Misc:

Example simple test:

File: testcatch2.cpp

#include <iostream>

#include <catch2/catch.hpp>


int formula(int x, int y)
{
    return 4 * x + 2 * y;
}

TEST_CASE("Test function furmula A", "[tag1]")
{
    REQUIRE( formula(3, 4) == 20 );
    REQUIRE( formula(4, 5) == 26 );
}

TEST_CASE("Test function formula B", "[tag2]")
{
    REQUIRE( formula(0, 0) == 0  );
    // Intentionally fails
    REQUIRE( formula(2, 5) == 10 );
}


struct TestData
{
    int x, y, expected;
};


TEST_CASE("Testing with struct")
{
    auto t = GENERATE( values<TestData>(
    {
        {3,  4, 20}
       ,{4,  5, 26}
       ,{0,  0,  0}
       ,{-1, 4,  6} // Fails
       ,{ 2, 2, 12}
    }));
    REQUIRE( formula(t.x, t.y) == t.expected);
}


TEST_CASE("Testing with structured bindings")
{
    auto [x, y, z] = GENERATE( table<int, int, int>(
    {
        {3,  4, 20}
       ,{4,  5, 26}
       ,{0,  0,  0}
       ,{2, 2, 12}
    }));
    REQUIRE( formula(x, y) == z);
}

File: CMakeLists.txt - CMake building script integrated with Conan.

cmake_minimum_required(VERSION 3.9)
project(Catch2_testing)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_VERBOSE_MAKEFILE ON)

# ============= Conan Bootstrap =============================#

# Download automatically, you can also just copy the conan.cmake file
if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake")
   message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan")
   file(DOWNLOAD "https://github.com/conan-io/cmake-conan/raw/v0.13/conan.cmake"
                 "${CMAKE_BINARY_DIR}/conan.cmake")
endif()

include(${CMAKE_BINARY_DIR}/conan.cmake)

conan_cmake_run(REQUIRES
                Catch2/2.7.2@catchorg/stable
                BASIC_SETUP
                BUILD missing
                )
#============= Find Packages ================================#

find_package(Catch2 REQUIRED)

#---------------------------------------------------#
#             Targets Settings                      #
#---------------------------------------------------#

add_executable(testcatch2 testcatch2.cpp test-main.cpp)
target_link_libraries(testcatch2)

Test runner help option:

$ ./testcatch2 -h

Catch v2.7.2
usage:
  testcatch2 [<test name|pattern|tags> ... ] options

where options are:
  -?, -h, --help                            display usage information
  -l, --list-tests                          list all/matching test cases
  -t, --list-tags                           list all/matching tags
  -s, --success                             include successful tests in
                                            output
  -b, --break                               break into debugger on failure
  -e, --nothrow                             skip exception tests
  -i, --invisibles                          show invisibles (tabs, newlines)
  -o, --out <filename>                      output filename
  -r, --reporter <name>                     reporter to use (defaults to
                                            console)
  -n, --name <name>                         suite name
  -a, --abort                               abort at first failure
  -x, --abortx <no. failures>               abort after x failures
  -w, --warn <warning name>                 enable warnings
  -d, --durations <yes|no>                  show test durations
  -f, --input-file <filename>               load test names to run from a
                                            file
  -#, --filenames-as-tags                   adds a tag for the filename
  -c, --section <section name>              specify section to run
  -v, --verbosity <quiet|normal|high>       set output verbosity
  --list-test-names-only                    list all/matching test cases
                                            names only
  --list-reporters                          list all reporters
  --order <decl|lex|rand>                   test case order (defaults to
                                            decl)
  --rng-seed <'time'|number>                set a specific seed for random
                                            numbers
  --use-colour <yes|no>                     should output be colourised
  --libidentify                             report name and version according
                                            to libidentify standard
  --wait-for-keypress <start|exit|both>     waits for a keypress before
                                            exiting
  --benchmark-resolution-multiple           multiple of clock resolution to
  <multiplier>                              run benchmarks

For more detailed usage please see the project docs


Test runner test list:

$ ./testcatch2 --list-tests
All available test cases:
  Test function furmula A
      [tag1]
  Test function formula B
      [tag2]
  Testing with struct
  Testing with structured bindings
4 test cases

Test runner output:

$ ./testcatch2

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
testcatch2 is a Catch v2.7.2 host application.
Run with -? for options

-------------------------------------------------------------------------------
Test function formula B
-------------------------------------------------------------------------------
/home/archbox/Documents/projects/fquant/testcatch2.cpp:17
...............................................................................

/home/archbox/Documents/projects/fquant/testcatch2.cpp:21: FAILED:
  REQUIRE( formula(2, 5) == 10 )
with expansion:
  18 == 10

-------------------------------------------------------------------------------
Testing with struct
-------------------------------------------------------------------------------
/home/archbox/Documents/projects/fquant/testcatch2.cpp:31
...............................................................................

/home/archbox/Documents/projects/fquant/testcatch2.cpp:41: FAILED:
  REQUIRE( formula(t.x, t.y) == t.expected )
with expansion:
  4 == 6

===============================================================================
test cases:  4 |  2 passed | 2 failed
assertions: 13 | 11 passed | 2 failed

1.9.3 Doctest - Unit Testing Framewok

Doctest is a unit test framework based on catch2 test framework, however doctest has a much faster compile-time than many other unit-test frameworks.

Advantages:

  • Easy of use
  • Faster compile-time
  • Easy to integrate, header-only-library
  • Operator based testing: CHECK(VALUE == EXPECTED), CHECK(x > 10) instead of No need to use macros ASSERT_EQ, ASSERT_NEQ (not equal) as in GTest.

Disadvantages:

  • No XML output with jUnit standard similar to GTest standard.
  • No integration with IDE
  • No data-driven test, aka parameterized test, or test with a list of value tuples.

Repository:

Conan Reference:

Conan Package:

Code Examples:

Features:

See:

Video:

Usage:

  • File: doctest_experiment.cpp
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <doctest.h>

int formula(int x, int y)
{
    return 4 * x + 2 * y;
}

TEST_CASE("Test function furmula A")
{
    CHECK( formula(3, 4) == 20 );
    CHECK( formula(4, 5) == 26 );
    CHECK( formula(4, 5) < 100 );
    CHECK( formula(4, 5) != 0 );
}

TEST_CASE("Test function formula B")
{
    CHECK( formula(0, 0) == 0  );
    // Intentionally fails
    CHECK( formula(2, 5) == 10 );
}
  • File: CMakeLists.txt integrated with Conan
cmake_minimum_required(VERSION 3.9)
project( doctest_experiment
         VERSION      0.1
         DESCRIPTION  "A doctest experiment"
        )
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_VERBOSE_MAKEFILE ON)

# ============= Conan Bootstrap =============================#

# Download automatically, you can also just copy the conan.cmake file
if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake")
   message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan")
   file(DOWNLOAD "https://github.com/conan-io/cmake-conan/raw/v0.13/conan.cmake"
                 "${CMAKE_BINARY_DIR}/conan.cmake")
endif()

include(${CMAKE_BINARY_DIR}/conan.cmake)

conan_cmake_run(REQUIRES
                doctest/2.3.1@bincrafters/stable
                BASIC_SETUP
                BUILD missing
                )

#============= Find Packages ================================#

find_package(doctest 2.3.1 REQUIRED)

#---------------------------------------------------#
#             Targets Settings                      #
#---------------------------------------------------#

add_executable(doctest_experiment doctest_experiment.cpp)
copy_after_build(doctest_experiment)
# target_link_libraries(doctest_experiment doctest::doctest)

Show options:

$ bin/doctest_experiment -h
[doctest] doctest version is "2.3.1"
[doctest]
[doctest] boolean values: "1/on/yes/true" or "0/off/no/false"
[doctest] filter  values: "str1,str2,str3" (comma separated strings)
[doctest]
[doctest] filters use wildcards for matching strings
[doctest] something passes a filter if any of the strings in a filter matches
[doctest]
[doctest] ALL FLAGS, OPTIONS AND FILTERS ALSO AVAILABLE WITH A "dt-" PREFIX!!!
[doctest]
[doctest] Query flags - the program quits after them. Available:

 -?,   --help, -h                      prints this message
 -v,   --version                       prints the version
 -c,   --count                         prints the number of matching tests
 -ltc, --list-test-cases               lists all matching tests by name
 -lts, --list-test-suites              lists all matching test suites
 -lr,  --list-reporters                lists all registered reporters

[doctest] The available <int>/<string> options/filters are:

 -tc,  --test-case=<filters>           filters     tests by their name
 -tce, --test-case-exclude=<filters>   filters OUT tests by their name
 -sf,  --source-file=<filters>         filters     tests by their file
 -sfe, --source-file-exclude=<filters> filters OUT tests by their file
 -ts,  --test-suite=<filters>          filters     tests by their test suite
 -tse, --test-suite-exclude=<filters>  filters OUT tests by their test suite
 -sc,  --subcase=<filters>             filters     subcases by their name
 -sce, --subcase-exclude=<filters>     filters OUT subcases by their name
 -r,   --reporters=<filters>           reporters to use (console is default)
 -o,   --out=<string>                  output filename
 -ob,  --order-by=<string>             how the tests should be ordered
                                       <string> - by [file/suite/name/rand]
 -rs,  --rand-seed=<int>               seed for random ordering
 -f,   --first=<int>                   the first test passing the filters to
                                       execute - for range-based execution
 -l,   --last=<int>                    the last test passing the filters to
                                       execute - for range-based execution
 -aa,  --abort-after=<int>             stop after <int> failed assertions
 -scfl,--subcase-filter-levels=<int>   apply filters for the first <int> levels

[doctest] Bool options - can be used like flags and true is assumed. Available:

 -s,   --success=<bool>                include successful assertions in output
 -cs,  --case-sensitive=<bool>         filters being treated as case sensitive
 -e,   --exit=<bool>                   exits after the tests finish
 -d,   --duration=<bool>               prints the time duration of each test
 -nt,  --no-throw=<bool>               skips exceptions-related assert checks
 -ne,  --no-exitcode=<bool>            returns (or exits) always with success
 -nr,  --no-run=<bool>                 skips all runtime doctest operations
 -nv,  --no-version=<bool>             omit the framework version in the output
 -nc,  --no-colors=<bool>              disables colors in output
 -fc,  --force-colors=<bool>           use colors even when not in a tty
 -nb,  --no-breaks=<bool>              disables breakpoints in debuggers
 -ns,  --no-skip=<bool>                don't skip test cases marked as skip
 -gfl, --gnu-file-line=<bool>          :n: vs (n): for line numbers in output
 -npf, --no-path-filenames=<bool>      only filenames and no paths in output
 -nln, --no-line-numbers=<bool>        0 instead of real line numbers in output

[doctest] for more information visit the project documentation

Test runner test listing:

$ bin/doctest_experiment --list-test-cases
[doctest] listing all test case names
===============================================================================
Test function furmula A
Test function formula B
================================================

Run tests: (Note: It is printed with colored output)

$ bin/doctest_experiment
[doctest] doctest version is "2.3.1"
[doctest] run with "--help" for options
===============================================================================
/home/archbox/Documents/projects/fquant/doctest_experiment.cpp:21:
TEST CASE:  Test function formula B

/home/archbox/Documents/projects/fquant/doctest_experiment.cpp:25: ERROR: CHECK( formula(2, 5) == 10 ) is NOT correct!
  values: CHECK( 18 == 10 )

===============================================================================
[doctest] test cases:      2 |      1 passed |      1 failed |      0 skipped
[doctest] assertions:      6 |      5 passed |      1 failed |
[doctest] Status: FAILURE!

1.10 Google Benchmark

Overview: It is library for micro benchmarks of isolated parts of the code and comparison between algorithms performance.

Web Site:

Sample Project:

File: CmakeLists.txt

cmake_minimum_required(VERSION 2.8)
project(gbench-evaluation)

set(CMAKE_CXX_STANDARD          17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) 
set(CMAKE_VERBOSE_MAKEFILE      ON)

#========== Macros for automating Library Fetching =============#

include(FetchContent)

# Download library archive (zip, *.tar.gz, ...) from URL
macro(Download_Library_Url NAME URL)
    FetchContent_Declare(${NAME} URL  ${URL})
    FetchContent_GetProperties(${NAME})
    if(NOT ${NAME}_POPULATED)
        FetchContent_Populate(${NAME})
        add_subdirectory(${${NAME}_SOURCE_DIR} ${${NAME}_BINARY_DIR})
    endif()
endmacro()


#========== Library Download / Fetching ===========================#

# Google Benchmark Build Setting 
set(BENCHMARK_ENABLE_TESTING        OFF CACHE BOOL "" FORCE)
set(BENCHMARK_ENABLE_GTEST_TESTS    OFF CACHE BOOL "" FORCE)
set(BENCHMARK_DOWNLOAD_DEPENDENCIES OFF CACHE BOOL "" FORCE)

Download_Library_Url(
        gbenchmark 
        "https://github.com/google/benchmark/archive/v1.5.0.zip" 
        )

#========== Package Settings =====================================#

add_executable(benchmark_runner bench.cpp)
target_link_libraries(benchmark_runner PRIVATE benchmark)

File: bench.cpp

#include <iostream>
#include <string>
#include <cstdint>
#include <cassert>
#include <fstream>

// Google Benchmark header
#include <benchmark/benchmark.h>

// #define ENABLE_LOGGING

using ulong = unsigned long;

//======= Implementations =================#

auto fibonacci_recursive(ulong n) -> ulong
{
    if(n < 2) { return 1; }
    return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2);
}

auto fibonacci_non_recursive(ulong n) -> ulong
{
    ulong a = 0, b = 1, next = 0;
    if(n < 2) { return 1; }
    for(auto i = 0UL; i < n; ++i)
    {
        next = a + b;
        a = b;
        b = next;
    }
    return next;
}


//====== Benchmark Code ===================#


/* Note: All benchmark functions must have this signature.
 *
 *  using BenchmarkSignature = void (*) (benchmark::State& state)
 */
void BM_fibonacci_non_recursive(benchmark::State& state)
{
    // std::cerr << " [NON RECURSIVE FIBONACCI] " << std::endl;

    while(state.KeepRunning())
    {
        for(int64_t n = 0, n_end = state.range(); n < n_end; n++) {
            fibonacci_non_recursive(static_cast<ulong>(n));
            // std::cerr << " n = " << n  << " Result = " << fibonacci_non_recursive(static_cast<ulong>(n)) << std::endl;
        }
    }
}

// Register benchmark function
// Possibility A: BENCHMARK(BM_fibonacci_non_recursive)->Range(0, 40);
BENCHMARK(BM_fibonacci_non_recursive)->Arg(10)->Arg(30)->Arg(40)->Arg(45);

#ifdef ENABLE_LOGGING
  auto sink = std::ofstream("logging.txt");
#endif


void BM_fibonacci_recursive(benchmark::State& state)
{
    // std::cerr << " [RECURSIVE FIBONACCI] " << std::endl;
    static int counter = 0;
    ulong result;

    #ifdef ENABLE_LOGGING
    sink << "\n [BM_fibonacci_recursive] counter = " << counter++ << std::endl;
    #endif

    while(state.KeepRunning())
    {
        for(int64_t n = 0, n_end = state.range(); n < n_end; n++) {
            auto result = fibonacci_recursive(static_cast<ulong>(n));
            #ifdef ENABLE_LOGGING
            sink << "n_end = " << n_end << " n = " << n << " Result = "
                 << fibonacci_recursive(static_cast<ulong>(n)) << std::endl;
            #endif

        }
    }
}

// Register benchmark function
BENCHMARK(BM_fibonacci_recursive)->Arg(10)->Arg(30)->Arg(40)->Arg(45);


//=============== Entry Point - main() Function =====================//

// Benchmark program entry point
//----------------------------
// BENCHMARK_MAIN();

int main(int argc, char** argv)
{
    std::cout << "========= Micro Benchmark ===============" << std::endl;
    benchmark::Initialize(&argc, argv);
    benchmark::RunSpecifiedBenchmarks();
    return 0;
}

Building

$ cmake -H. -B_build -DCMAKE_BUILD_TYPE=Release
$ cmake --build _build --target

Running

$ _build/benchmark_runner 
========= Micro Benchmark ===============
2019-08-28 14:53:53
Running _build/benchmark_runner
Run on (4 X 3100 MHz CPU s)
CPU Caches:
  L1 Data 32K (x2)
  L1 Instruction 32K (x2)
  L2 Unified 256K (x2)
  L3 Unified 4096K (x1)
Load Average: 0.72, 0.81, 0.96
***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead.
------------------------------------------------------------------------
Benchmark                              Time             CPU   Iterations
------------------------------------------------------------------------
BM_fibonacci_non_recursive/10      0.375 ns        0.373 ns   1000000000
BM_fibonacci_non_recursive/30      0.367 ns        0.366 ns   1000000000
BM_fibonacci_non_recursive/40      0.352 ns        0.351 ns   1000000000
BM_fibonacci_non_recursive/45      0.357 ns        0.356 ns   1000000000
BM_fibonacci_recursive/10            261 ns          260 ns      2531115
BM_fibonacci_recursive/30        4407109 ns      4396380 ns          159
BM_fibonacci_recursive/40      600690587 ns    598780692 ns            1
BM_fibonacci_recursive/45     6128444226 ns   6110706127 ns            1

1.11 Pistache - REST Http Web Server

Pistache is C++11 a web, http and rest framework, in other words, a C++ toolkit for building standalone web servers. This library is easy to use and fast to compile without any external dependency.

Web Site:

Repository:

Conan Coordinate:

Example:

File: CMakeLists.txt

cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
project(pistache-web-server)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_VERBOSE_MAKEFILE ON)

# ============= Conan Bootstrap =============================#

# Download automatically, you can also just copy the conan.cmake file
if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake")
   message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan")
   file(DOWNLOAD "https://github.com/conan-io/cmake-conan/raw/v0.13/conan.cmake"
                 "${CMAKE_BINARY_DIR}/conan.cmake")
endif()

include(${CMAKE_BINARY_DIR}/conan.cmake)

conan_cmake_run(REQUIRES
                pistache/d5608a1@conan/stable
                BASIC_SETUP
                BUILD missing
                )

# ========= Target Configurations =========================// 

add_executable(pistache-server pistache-server.cpp)
target_link_libraries(pistache-server pistache)

File: pistache-server.cpp

#include <iostream>
#include <string>
#include <pistache/endpoint.h>

namespace ps = Pistache;
namespace Http = Pistache::Http;
namespace Header = Pistache::Http::Header;

// using namespace Pistache;

std::ostream&
operator<<(std::ostream& os, Http::Request const& req)
{
    return os << " Method " << req.method()
              << "; route =  " << req.resource()
              << "; body = " << req.body();

}

struct HttpTransaction : public ps::Http::Handler
{
    HTTP_PROTOTYPE(HttpTransaction)

    void
    onRequest(Http::Request const& req, Http::ResponseWriter res) override
    {
        std::cout << " Request: " << req << std::endl;

        // Set response headers
        res.headers().add<Header::Server>("CeePlusPlusWebServer");

        // Route: / => Root
        if(req.method() == Http::Method::Get && req.resource() == "/")
        {
            res.send(Http::Code::Ok, " ==> Welcome to my C++ Web Server, Enjoy it");
            return;
        }

        // Route: /demo1
        if(req.method() == Http::Method::Get && req.resource() == "/demo1")
        {
            res.send(ps::Http::Code::Ok,
                     R"(
                      <html>
                         <h1> C++ blazing fast web server. </h1>
                         <img src='https://www.howtogeek.com/wp-content/uploads/2018/09/bin_lede.png.pagespeed.ce.cyJdjIBBg2.png' />
                      </html>
                     )");
            return;
        }

        // Route: /file => Serve Linux file /etc/protcols to client side
        if(req.method() == Http::Method::Get && req.resource() == "/file")
        {
            // Allows client side download the text file /etc/protocols
            Http::serveFile(res, "/etc/protocols");
            return;
        }

        res.send(Http::Code::Not_Found, "Error: page not found in this server");

    }
};

int main()
{
    std::puts(" [INFO] Starting server. ");

    auto addr = ps::Address{
        // Allows connections from any IP address
        ps::Ipv4::any(),
        // Listen port 9080, aka bind to port 9080
        ps::Port(9080)
    };

    auto ops = ps::Http::Endpoint::options()
                   .threads(4)
                   .flags( ps::Tcp::Options::ReuseAddr);


    ps::Http::Endpoint server(addr);
    server.init(ops);

    server.setHandler(Http::make_handler<HttpTransaction>());

    try
    {
        // Attempt to bind server to port
        server.serve();
    } catch(std::runtime_error const& ex)
    {
        std::cout << " [ERROR] Unable to start server. => \n"
                  << ex.what() << std::endl;
    }

    std::puts("Exit application. OK.");
    return 0;

} // --- End of Main() ---------------------//

Running server:

$ ./bin/pistache-server 
 [INFO] Starting server. 

Server route: /

  • Http request to URL: http://localhost:9080/
 $ curl  localhost:9080
 Welcome to my C++ Web Server, Enjoy it

$ curl -v localhost:9080
* Rebuilt URL to: localhost:9080/
*   Trying ::1...
* TCP_NODELAY set
* connect to ::1 port 9080 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 9080 (#0)
> GET / HTTP/1.1
> Host: localhost:9080
> User-Agent: curl/7.59.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Server: CeePlusPlusWebServer 
< Connection: Keep-Alive
< Content-Length: 38
< 
* Connection #0 to host localhost left intact
Welcome to my C++ Web Server, Enjoy it
  • Server log:
Request:  Method GET; route =  /; body = 

Server route: /errorrout

  • Http request to URL: http://localhost:9080/errorroute
$ curl  http://localhost:9080/errorroute
Error: page not found in this server

$ curl -v  http://localhost:9080/errorroute
*   Trying ::1...
* TCP_NODELAY set
* connect to ::1 port 9080 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 9080 (#0)
> GET /errorroute HTTP/1.1
> Host: localhost:9080
> User-Agent: curl/7.59.0
> Accept: */*
> 
< HTTP/1.1 404 Not Found
< Server: CeePlusPlusWebServer 
< Connection: Keep-Alive
< Content-Length: 36
< 
* Connection #0 to host localhost left intact
Error: page not found in this server

Server route: /demo1

  • Http request to URL: http://localhost:9080/demo1
$ curl http://localhost:9080/demo1

                      <html>
                         <h1> C++ blazing fast web server. </h1>
                         <img src='https://www.howtogeek.com/wp-content/uploads/2018/09/bin_lede.png.pagespeed.ce.cyJdjIBBg2.png' />
                      </html>


$ curl -v  http://localhost:9080/demo1
*   Trying ::1...
* TCP_NODELAY set
* connect to ::1 port 9080 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 9080 (#0)
> GET /demo1 HTTP/1.1
> Host: localhost:9080
> User-Agent: curl/7.59.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Server: CeePlusPlusWebServer 
< Connection: Keep-Alive
< Content-Length: 279
< 

                      <html>
                         <h1> C++ blazing fast web server. </h1>
                         <img src='https://www.howtogeek.com/wp-content/uploads/2018/09/bin_lede.png.pagespeed.ce.cyJdjIBBg2.png' />
                      </html>
* Connection #0 to host localhost left intact

Server route: /file

  • Http request to URL: http://localhost:9080/file
$ curl http://localhost:9080/file
# /etc/protocols:
# $Id: protocols,v 1.12 2016/07/08 12:27 ovasik Exp $
#
# Internet (IP) protocols
#
#       from: @(#)protocols     5.1 (Berkeley) 4/17/89
#
# Updated for NetBSD based on RFC 1340, Assigned Numbers (July 1992).
# Last IANA update included dated 2011-05-03
#
# See also http://www.iana.org/assignments/protocol-numbers

ip      0       IP              # internet protocol, pseudo protocol number
hopopt  0       HOPOPT          # hop-by-hop options for ipv6

  ... ...   ... ...   ... ...   ... ...   ... ...   ... ... 
  ... ...   ... ...   ... ...   ... ...   ... ...   ... ... 

hip     139     HIP             # Host Identity Protocol
shim6   140     Shim6           # Shim6 Protocol
wesp    141     WESP            # Wrapped Encapsulating Security Payload
rohc    142     ROHC            # Robust Header Compression
#   143-252 Unassigned                                       [IANA]
#   253     Use for experimentation and testing           [RFC3692]
#   254     Use for experimentation and testing           [RFC3692]
#   255                 Reserved                             [IANA]

1.12 Httplib - Http Client and Server Library

Cpp-Httplib is a single-file and header-only library for building HTTP client or HTTP server applications. The advantage of this library is that unlike most HTTP libraries, it does not relies on LibCurl or on Boost.ASIO which makes Cpp-Httplib easier to setup and add to new projects.

Repository:

Dependencies:

  • SSL Library for HTTPS
    • Or OpenSSL
    • Or LibreSSL (OpenSSL fork from OpenBSD)
    • Or WolfSSL (SSL for embedded applications)
  • LibZ for Compression
  • Pthread (Posix Threads) on Unix-like operating systems

Sample project

Gist:

File: CMakeLists.txt

cmake_minimum_required(VERSION 3.9)
project(httplib-client)

#========== Global Configurations =============#
#----------------------------------------------#

set(CMAKE_CXX_STANDARD 17)     
set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# ------------ Download CPM CMake Script ----------------#

## Automatically donwload and use module CPM.cmake
file(DOWNLOAD https://raw.githubusercontent.com/TheLartians/CPM.cmake/v0.26.2/cmake/CPM.cmake
                 "${CMAKE_BINARY_DIR}/CPM.cmake")
include("${CMAKE_BINARY_DIR}/CPM.cmake")

#----------- Add dependencies --------------------------#

CPMAddPackage(
    NAME  httplib 
    URL  "https://github.com/yhirose/cpp-httplib/archive/v0.6.6.zip"
)
include_directories( ${httplib_SOURCE_DIR} )

find_package(PkgConfig REQUIRED)
pkg_search_module(OPENSSL REQUIRED openssl)

message([TRACE] " httplib source = ${httplib_SOURCE_DIR} ")
message([TRACE] " OPENSSL_LIBRARIES = ${OPENSSL_LIBRARIES} ")

#----------- Set targets -------------------------------#
add_executable(client client.cpp)

# Requires implementation of this for Windows.
IF(UNIX)
    target_link_libraries(client pthread z ${OPENSSL_LIBRARIES} )
ENDIF()

File: client.cpp

#include <iostream>
#include <cassert>
#include <functional>
#include <fstream>

#define CPPHTTPLIB_ZLIB_SUPPORT
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include <httplib.h>

namespace h = httplib;

template<typename T> using shp = std::shared_ptr<T>;

int main(int argc, char** argv)
{
    std::cout << "\n [TRACE] Started Ok. " << "\n\n";

    // -------- E X P E R I M E N T / 1 --------------------------------------//
    //------------------------------------------------------------------------//
    std::cout << " [EXPERIMENT 1] HttpS (HTTP + SSL) Request " << '\n';
    std::puts(" ----------------------------------------------------");

    {
        h::SSLClient cli("www.httpbin.org");
        // cli.enable_server_certificate_verification(true);

        shp<h::Response> res = cli.Get("/get");
        assert( res != nullptr );

        if(res)
        {
            std::cout << "   => Http Version = " << res->version << '\n';
            std::cout << "   =>       Status = " << res->status << '\n';
            std::cout << "   => Content-Type = " << res->get_header_value("Content-Type") << '\n';

            std::puts(" --- All HTTP headers -----------");
            for( auto const& [key, value] : res->headers )
                std::cout << std::setw(40) << std::right << key
                          << "  "
                          << std::setw(35) << std::left  << value
                          << "\n";


            if(res->status == 200)
                std::cout << "\n [INFO] HTTP Response = " << res->body << '\n';

        }
    }

    // -------- E X P E R I M E N T / 1 --------------------------------------//
    //------------------------------------------------------------------------//
    std::cout << " [EXPERIMENT 2] Https (HTTP + SSL) ==>> Download binary file " << '\n';
    std::puts(" ----------------------------------------------------");

    auto ifs = std::ofstream("image.jpeg");

    auto cli = h::Client2("https://www.httpbin.org");
    auto res = cli.Get("/image/jpeg"
                       ,[&](const char* bytes, size_t len) -> bool
                        {
                            ifs.write(bytes, len);
                             // Note: return false for stopping processing the request
                            return true;
                        });

    // Status 200 means successful HTTP response from server.
    std::cout << " Res->status = " << res->status << '\n';

    std::cout << "\n [TRACE] Finish Ok. " << "\n";
    return 0;
}

Building:

$ git clone https://gist.github.com/caiorss/d890edcb1cf3814ffcd6915940e4f2ca code && cd code

$ ls 
client.cpp  CMakeLists.txt

$ cmake -H. -B_build -DCMAKE_BUILD_TYPE=Debug 
$ cmake --build _build --target 

Check compilation output: (object code)

 $ file _build/client 
 _build/client: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter 
/lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e2aa2ba8c5258b274b1c58ddd6039c2f9c83f995, for GNU/Linux 3.2.0, 
with debug_info, not stripped

# Shared library dependencies
$ ldd _build/client 
        linux-vdso.so.1 (0x00007ffffa9cd000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fbbbc4f5000)
        libz.so.1 => /lib64/libz.so.1 (0x00007fbbbc4db000)
        libssl.so.1.1 => /lib64/libssl.so.1.1 (0x00007fbbbc444000)
        libcrypto.so.1.1 => /lib64/libcrypto.so.1.1 (0x00007fbbbc157000)
        libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007fbbbbf67000)
        libm.so.6 => /lib64/libm.so.6 (0x00007fbbbbe21000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fbbbbe04000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fbbbbc3a000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fbbbc53b000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007fbbbbc33000)

Running:

$ _build/client 

 [TRACE] Started Ok. 

 [EXPERIMENT 1] HttpS (HTTP + SSL) Request 
 ----------------------------------------------------
   => Http Version = HTTP/1.1
   =>       Status = 200
   => Content-Type = application/json
 --- All HTTP headers -----------
        Access-Control-Allow-Credentials  true                               
             Access-Control-Allow-Origin  *                                  
                              Connection  close                              
                          Content-Length  293                                
                            Content-Type  application/json                   
                                    Date  Mon, 08 Jun 2020 17:15:26 GMT      
                                  Server  gunicorn/19.9.0                    

 [INFO] HTTP Response = {
  "args": {}, 
  "headers": {
    "Accept": "*/*", 
    "Content-Length": "0", 
    "Host": "www.httpbin.org", 
    "User-Agent": "cpp-httplib/0.6", 
    "X-Amzn-Trace-Id": "Root=2-5ede733a-52cceb0745415a696bea025d"
  }, 
  "origin": "153.8.52.56", 
  "url": "https://www.httpbin.org/get"
}

 [EXPERIMENT 2] Https (HTTP + SSL) ==>> Download binary file 
 ----------------------------------------------------
 Res->status = 200

 [TRACE] Finish Ok. 

Created: 2021-06-04 Fri 15:06

Validate