CPP / C++ Notes - C++17 New Features and Containers

Table of Contents

1 C++ New Features

1.1 Reference Implementations

1.1.1 Overview

Reference implementations of C++ libraries from C++17, C++20 and upcoming C++ standards. Note: Some C++ compilers still not provide those libraries and most compilers still does not provide the C++17 parallel algorithms, which allows taking advantage of multi-core processors and SIMD CPU instructions.

1.1.2 C++17 Standard

std::optional - C++17

  • Standard Library Reference
    • utility/optional
    • "The class template std::optional manages an optional contained value, i.e. a value that may or may not be present. A common use case for optional is the return value of a function that may fail. As opposed to other approaches, such as std::pair<T,bool>, optional handles expensive-to-construct objects well and is more readable, as the intent is expressed explicitly."
  • Reference Implementation

std::variant - C++17

  • Standard Library Reference
    • utility/variant - CppReference - "The class template std::variant represents a type-safe union. An instance of std::variant at any given time either holds a value of one of its alternative types, or in the case of error - no value (this state is hard to achieve, see valueless_by_exception). As with unions, if a variant holds a value of some object type T, the object representation of T is allocated directly within the object representation of the variant itself. Variant is not allowed to allocate additional (dynamic) memory. A variant is not permitted to hold references, arrays, or the type void. Empty variants are also ill-formed (std::variant<std::monostate> can be used instead)."
  • Reference Implementation:

Parallel Algorithms of C++17

1.1.3 C++20 Standard

C++20 std::span

C++20 Ranges

  • Standard Library Reference:
  • Reference Implementation:
    • https://github.com/ericniebler/range-v3
    • "Range library for C++14/17/20. This code was the basis of a formal proposal to add range support to the C++ standard library. That proposal evolved through a Technical Specification, and finally into P0896R4 "The One Ranges Proposal" which was merged into the C++20 working drafts in November 2018."

C++20 Format Library

C++20 jthread

1.2 C++17 New Features and Containers

1.2.1 Structured Bindings

C++17 structured bindings allows to decompose data structures such as pairs, tuples, C-arrays and structs or classes with public fields in an uniform way.

Example:

Compiling and running:

$ clang++ -std=c++1z -g -Wall -Wextra cpp17-structured-bindings.cpp -o cpp17-structured-bindings.bin  
$ ./cpp17-structured-bindings.bin

Parts in Main Function

  • Experiment 1 - Decompose pair.
std::puts("\n=== EXPERIMENT 1: Decompose: pair - binding - pair =====");
auto p = std::pair<std::string, int>("C++", 17);
auto [name, version] = p;
std::cout << " name = " << name << " ; version = " << version << std::endl;

Output:

=== EXPERIMENT 1: Decompose: pair - binding - pair =====
 name = C++ ; version = 17
  • Experiment 2 - Decompose: map / 'hash-table'
std::puts("\n=== EXPERIMENT 2: Decompose: map / 'hash-table' - =====");
auto database = std::map<std::string, int>{{"c++", 17}, {"rust", 10}, {"Forth", 200}};
for(const auto& [key, value] : database)
        std::cout << "key = " << key << " ; value = " << value << std::endl;

Output:

=== EXPERIMENT 2: Decompose: map / 'hash-table' - =====
key = Forth ; value = 200
key = c++ ; value = 17
key = rust ; value = 10
  • Experiment 3 - Decompose: tuple
std::puts("\n=== EXPERIMENT 3: Decompose: tuple - =====");
using DatabaseRow = std::tuple<int, std::string, double>;
auto database = std::vector<DatabaseRow>{
        {100, "Fried tasty fresh cheese",   3.45},
        {400, "Super hot toasted coffee.", 6.25},
        {500, "Fresh Orange Juice",        4.50},
};

for(const auto& [id, name, price]: database)
        std::cout << " ROW=> id = " << id
                  << " ; name = "   << name
                  << " ; price = "  << price 
                  << std::endl;

Output:

=== EXPERIMENT 3: Decompose: tuple - =====
 ROW=> id = 100 ; name = Fried tasty fresh cheese ; price = 3.45
 ROW=> id = 400 ; name = Super hot toasted coffee. ; price = 6.25
 ROW=> id = 500 ; name = Fresh Orange Juice ; price = 4.5
  • Experiment 4 - Decompose Structs
std::puts("\n=== EXPERIMENT 4: Decompose: Structs - =====");
struct GeoCoordinate{
        std::string name;
        double      latitude;
        double      longitude;
};

auto geoDatabase = std::deque<GeoCoordinate>{
         {"Bogota",         4.7110,  -74.0721}
        ,{"São Paulo",    -23.5505,  -46.6333}
        ,{"Gauteng",      -26.2708,   28.1123}          
        ,{"Buenos Aires", -34.6037,  -58.3816}
        ,{"Brasilia",     -15.8267,  -47.9218}
};
std::cout << std::setprecision(3);

std::for_each(geoDatabase.begin(), geoDatabase.end(),
              [](const auto& city){
                  // Decompose struct into name, latitude and longitude
                  const auto& [name, lat, lon] = city;
                  std::cout << std::setw(15) << std::left  << name
                            << std::setw(8) << std::right << lat
                            << std::setw(8) << lon 
                            << "\n";                          
              }); 

Output:

=== EXPERIMENT 4: Decompose: Structs - =====
Bogota             4.71   -74.1
Sâo Paulo         -23.5   -46.6
Gauteng           -26.3    28.1
Buenos Aires      -34.6   -58.4
Brasilia          -15.8   -47.9
  • Decompose: C-Array
std::puts("\n=== EXPERIMENT 5: Decompose: C-Array - =====");
double array [3] = {10.23, 90.23, 100.0};
auto [x, y, z] = array;
std::printf(" array { x = %.3f ; y = %.3f ; z = %.3f }", x, y, z);

Output:

=== EXPERIMENT 5: Decompose: C-Array - =====
 array { x = 10.230 ; y = 90.230 ; z = 100.000 }

1.2.2 Functions and algorithms

  1. Algorithm std::clamp

    Clamps the the input value to range [LOW, HIGH]:

    Pseudo-Signature:

    T std::clamp(T input, T low, T high);
    

    Algorithm:

    + if INPUT < LOW        => returns LOW
    + if INPUT > HIGH       => returns HIGH
    + If LOW < INPUT < HIGH => returns INPUT 
    

    This function could be implemented as:

    template<typename T>
    auto clamp(T const& input, T const& low, T const& high) ->T 
    {
            return std::max(low, std::min(high, input));
    }
    

    Testing code:

    • If any assert predicate statement evaluates to false, it will cause an runtime error that will shutdown the application and show an error message with the error location and the assertion failure.
    #include <iostream>
    #include <algorithm>
    #include <cassert>
    
    int main (void)
    {
        assert(30 == std::clamp(10, 30, 100));
        assert(50 == std::clamp(50, 30, 100));
        assert(60 == std::clamp(60, 30, 100));
        assert(100 == std::clamp(100, 30, 100));
        assert(100 == std::clamp(160, 30, 100));
    
        std::puts(" [INFO] All tests passed.  OK");
        return 0;
    }
    
  2. Function std::apply - apply function to tuple

    Apply a function to a tuple:

    Example:

    #include <iostream>
    #include <tuple>       // Import: std::tuple
    #include <utility>     // Import: std::appply
    
    using Coord2d = std::tuple<std::string, float, float>;
    
    void print_coord2d(std::string const& name, float x, float y)
    {
        std::printf(" Coord2D{ name = %s ; x = %.3f; y = %.3f } \n", name.c_str(), x, y);
    }
    
    // Higher order function => Returns a lambda that applies a function of
    // (string, float, float) into a tuple argument.
    template<typename Function>
    auto make_function(Function func)
    {
        return [=](Coord2d const& coord){
            std::apply(func, coord);
        };
    }
    
    int main (void)
    {
        auto p1 = std::make_tuple("p1", -20.415f, 9.01f);
        Coord2d p2 { "p2", 10.251, 89.245};
        Coord2d p3 = { "p3", 10.251, 89.245};
        // Calls move constructor
        auto p4 = Coord2d { "p4", 20.671, 90.156};
    
        std::apply(print_coord2d, p1);
        std::apply(print_coord2d, p2);
        std::apply(print_coord2d, p3);
        std::apply(print_coord2d, p4);
    
        std::cout << "\n";
    
        std::apply([](std::string const& name, float x, float y)
                   {
            std::cout << " Plot point { "
                      <<  name << " ; "
                      << x << y << " } " << "\n";
                   },
                   p1);
    
        std::cout << "\n ==== Test transformed functions ===== " << std::endl;
    
        auto tuple_printer = make_function(print_coord2d);
    
        tuple_printer(p1);
        tuple_printer(p2);
    
        return 0;
    }
    

    Output:

    Coord2D{ name = p1 ; x = -20.415; y = 9.010 } 
    Coord2D{ name = p2 ; x = 10.251; y = 89.245 } 
    Coord2D{ name = p3 ; x = 10.251; y = 89.245 } 
    Coord2D{ name = p4 ; x = 20.671; y = 90.156 } 
    
    Plot point { p1 ; -20.4159.01 } 
    
    ==== Test transformed functions ===== 
    Coord2D{ name = p1 ; x = -20.415; y = 9.010 } 
    Coord2D{ name = p2 ; x = 10.251; y = 89.245 } 
    
  3. Function std::invoke - invoke anything callable

    Function which provides an uniform interface for invoking anything callable such as class member functions (ordinary methods), static member functions (static methods), functios and etc.

    See: https://en.cppreference.com/w/cpp/utility/functional/invoke

    Headers:

    #include <iostream>
    #include <string>
    #include <ostream>
    
    // std::invoke is provide by header functional
    #include <functional> 
    

    Class Dummy:

    struct Dummy{
        double evalme(double x) {
            std::cerr << __FILE__ << ":" << __LINE__ << " I was evaluated ; 2x = " << 2 *x << '\n';
            return 2 * x;
        }
        double operator()(double x){
            std::cerr << __FILE__ << ":" << __LINE__ << " Call function-operator << 4 * x = " << 4 * x << '\n';
            return 4 * x;
        }
    };
    

    Function computeDouble:

    double computeDouble(double x){
        std::cerr << __FILE__ << ":" << __LINE__ << " Computed double of 2x = " << 2 * x << '\n';
        return 2 * x;
    }
    

    Main function:

    int main(){
        std::invoke(computeDouble, 3.0);
        Dummy dummy;
        std::invoke(dummy, 3.0);
        std::invoke(Dummy(), 2.0);
        // Call method: .evalme indirectly 
        std::invoke(&Dummy::evalme, dummy, 3.0);
        return 0;
    }
    

    Compile and run:

    $ g++ cpp17-invoke.cpp -o out.bin -std=c++1z -Wall -Wextra && ./out.bin
    
    cpp17-invoke.cpp:20 Computed double of 2x = 6
    cpp17-invoke.cpp:14 Call function-operator << 4 * x = 12
    cpp17-invoke.cpp:14 Call function-operator << 4 * x = 8
    cpp17-invoke.cpp:9 I was evaluated ; 2x = 6
    

1.2.3 Attributes

Attributes are annotations that provide unified syntax to language extensions and additional information to compilers that can be used for issue warning when certain conditions are met, apply special logic to the code or perform optimization.

The C++11 and C++17 standard defines the following new attributes:

  • noreturn
    • Annoation indicating that the function does not return.
  • deprecated or deprecated("reason")
    • Indicates that the usage of the function or member function is discouraged issuing a compile warning when the annotated code is used.
  • falltrhough (C++17)
  • nodiscard (C++17)
  • maybe_unused (C++17)
    • Disables compile warning for unnused function parameters, variables or member variables.

Examples of Language/Compiler Extensions which attributes could replace

GNU GCC or G++ and Clang LLVM

__attribute___(aligned(16)) class Something{ ... };

double variable __attribute__((unnused));

MSV Visual C++ Compiler and MingW (GCC ported to Windows)

  // Function exportd from DLL 
__declspec(dllexport) bool function_export() { ... }

 // Function imported from DLL 
__declspec(dllimport) void function_imported() { ... }

class __declspec(dllexport)  ClassExported
{
  // .... code here ... //
};

Example of Attribute Usage:

  • Attribute depreacted
[[deprecated]]
bool check_disk()
{
    // ... code here ...
    return true; 
}

[[deprecated("DO NOT USE IT. It will phased out on next release.")]]
void process_function()
{
  /* ... Code here ... */    
}
  • Attribute noreturn
void process_action(std::string const&);

[[noreturn]]
void process_input_event_loop()
{
    std::string line;
    // Infinite loop function never returns 
    for (;;) {
        std::getline(std::cin, line);
        if(line == "exit") { std::puts(" Ciau."); std::exit(0); }
        process_action(line);
    };

}
  • Attribute maybe_unused (C++17)
    • Suppress warnings for unused function parameters.
double compute_sum(size_t size, double xs [], [[maybe_unused]] int flags)
{
    double sum = 0.0;
    for(size_t i = 0; i < size; ++i) { sum+= xs[i]; }
    return sum; 
}
  • Attribute nodiscard (C++17)
    • Raises a compiler warning when a return value of an annoated function is discarded. It is useful to annotate functions which returns error codes when exceptions cannot be used.
[[nodiscard]]
bool set_temperature(int t)
{
    std::cout << " Set oven temperature to: " << t << " Celsius" << "\n";
    // ... some code  ... //
    return false;
}

int main() 
{
   // Raises compiler warning 
   set_temperature(100); 

   // Do not raise compiler WARNING 
   if(set_temperature(100)) 
   { 
      // do something else ... 
   } else 
   {
      // Report failure to end use. 
   }

   return 0; 
}

See:

1.2.4 std::any

This example shows how to use the C++17 std::any container which comes froom boost::any.

See: https://en.cppreference.com/w/cpp/utility/any

File: file:src/cpp17/cpp17-any.cpp

#include <iostream>
#include <string>
#include <iomanip>
#include <ostream>

#include <any>

struct Point{
    double x;
    double y;
    Point(double x, double y): x(x), y(y) {}

    // Copy constructor
    Point(const Point& p){
        std::cerr << " -->> Copy constructor" << '\n';
        x = p.x;
        y = p.y;
    }   
};

std::ostream& operator<<(std::ostream& os, const Point& p){
    os << "Point(" << p.x << ", " << p.y << ") ";
    return os;
}

template<typename T>
auto printInfo(std::any x) -> void{
    std::cout << " x.type = " << x.type().name()
              << " ; value(x) = "
              << std::any_cast<T>(x)
              << '\n';  
}

int main(){
    // Print boolean as 'true', 'false', instead of 0 or 1
    std::cout << std::boolalpha;
    std::any x = 1;
    printInfo<int>(x);
    x = 10.233;
    printInfo<double>(x);
    x = 'k';
    printInfo<char>(x);
    x = "hello world";
    printInfo<const char*>(x);
    x = std::string("hello world");
    printInfo<std::string>(x);  
    x = Point(100.0, 20.0);
    printInfo<Point>(x);
    std::cout << "Has value: x.has_value() = " << x.has_value() << '\n';
    x.reset();                                                         
    std::cout << "Has value: x.has_value() = " << x.has_value() << '\n';                                                               
    std::cout << "Try casting " << std::endl;
    x = "testing type casting";
    try{
        std::any_cast<int>(x);
    } catch (const std::bad_any_cast& ex) {
        std::cerr << " >>> Exception: what = " << ex.what() << '\n'; 
    }
    std::cerr << " >>> End the program gracefully" << '\n'; 
    return 0;
}

Compiling with gcc:

$ g++ --version
g++ (GCC) 7.3.1 20180130 (Red Hat 7.3.1-2)
.. ... ... ... 

$ g++ cpp17-any.cpp -o out.bin -std=c++1z -Wall -Wextra && ./out.bin
 x.type = i ; value(x) = 1
 x.type = d ; value(x) = 10.233
 x.type = c ; value(x) = k
 x.type = PKc ; value(x) = hello world
 x.type = NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE ; value(x) = hello world
 -->> Copy constructor
 -->> Copy constructor
 x.type = 5Point ; value(x) =  -->> Copy constructor
Point(100, 20) 
Has value: x.has_value() = true
Has value: x.has_value() = false
Try casting 
 >>> Exception: what = bad any_cast
 >>> End the program gracefully

Compile with MSVC / VC++ on Windows:

$ cl.exe cpp17-any.cpp /EHsc /Zi /nologo /std:c++17 /Fe:out.exe && out.exe
cpp17-any.cpp
 x.type = int ; value(x) = 1
 x.type = double ; value(x) = 10.233
 x.type = char ; value(x) = k
 x.type = char const * __ptr64 ; value(x) = hello world
 x.type = class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > ; value(x) = hello world
 -->> Copy constructor
 -->> Copy constructor
 -->> Copy constructor
 x.type = struct Point ; value(x) = Point(100, 20) 
Has value: x.has_value() = true
Has value: x.has_value() = false
Try casting 
 >>> Exception: what = Bad any_cast
 >>> End the program gracefully

1.2.5 std::optional

  1. Overview

    The container std::optional allows expressing in a concise and type-safe way that a function may return nothing, the function parameter may be empty or that a class field may be empty as well. This type is a disjoint union similar to Haskell's Maybe or OCaml optional. A common approach used by many C++ APIs for dealing with empty return values is to return a pointer to the returned value and return null pointer when no value is found or the function retuned nothing. The drawback of this approach is the possibility of null pointer dereference which is a undefined behavior and may unexpectedly crash the program. In other language this common problem is known as the infamous null pointer exception.

    Documentation:

    Constructors:

    Create empty std::optional object (default).

    std::optional<T> value;
    

    Create an empty std::optional object (std::nullopt) explicity:

    std::optional<T> value = std::nullopt;
    std::optional<T> value{std::nullopt};
    std::optional<T> value = {}
    
    auto value = std::optional<int> {std::nullopt};
    auto value = std::optional<int> {};
    

    Create a std::optional instance set to some value

    std::optional<int> value = 100;
    std::optional<int> value{100};
    auto value = std::optional<int> {100};
    

    Check whether std::optional instance has a value or not

    std::optional<int> x = 100;
    if(x) 
      std::cout << "x is NOT EMPTY x = " << x.value() << std::endl;
    else
      std::cout << "x is EMPTY. " << std::endl;
    
  2. Example

    Source:

    Compiling and running:

    $ clang++ optional1.cpp -o optional1.bin -std=c++1z -g -O0 -Wall 
    $ ./optional1.bin
    

    Headers:

    #include <iostream>
    #include <string>  // std::stof 
    #include <ostream> // operator (<<) and class ostream 
    #include <vector>
    #include <iomanip> // setprecision
    
    #include <optional>
    

    Class Location contains an optional field:

    // Class with optional field.
    struct Location{
         double lat;
         double lon;
         std::optional<std::string> name; 
    };
    

    Insertion operator overload to make the class Location printable:

    // Make std::optional<Location> printable 
    std::ostream& operator<<(std::ostream& os, const Location& x){
         os << std::setprecision(2) << std::fixed;
         if(x.name)
            os << "Location("
               << "x = " << x.lat
               << " ; y = " << x.lon
               << " ; name = "
               << x.name.value() << ")";
         else
            os << "Location("
               << "x = " << x.lat
               << " ; y = " << x.lon
               << " ; name = "
               << "<unknown>" << ")";       
         return os;  
    }
    

    Insertion operator overload (<<) for making the value std::optiona<double> printable:

    // Make std::optional<double> printable
    std::ostream& operator<<(std::ostream& os, const std::optional<double>& x){
        if(x)
           os << "Some[Double](" << x.value() << ")";
        else
           os << "None[Double]";
        return os;  
    }
    

    Function parseDouble may return nothing if the function fails:

    std::optional<double>
    parseDouble(const std::string& str)  
    {
        try{
        // Return some value 
            return std::make_optional(stod(str));
        } catch (const std::invalid_argument& ex) {
        // Return nothing 
            return std::nullopt;
        }
    }
    

    Function: getEnvVar attempts to return the value environment variable, if it fails returns nothing:

    auto getEnvVar(std::string const& varname) -> std::optional<std::string>
    {
        if(const char* v = std::getenv(varname.c_str()))
             // Returns some value 
             return v;
        else
             // Returns nothing (same as std::nullopt)
             return {};
    }  
    

    Function main:

    • Experiment 1: Print optional values
    char endl = '\n';
    
    std::optional<double> oa;
    std::optional<double> ob = std::nullopt;
    std::optional<double> oc = 10.233;
    std::optional<double> od(1e3);
    std::optional<double> oe {};
    
    std::puts("==== EXPERIMENT 1 === Print optional value");
    std::cout << "oa = " << oa << endl;
    std::cout << "ob = " << ob << endl;
    std::cout << "oc = " << oc << endl;
    std::cout << "od = " << od << endl;
    std::cout << "oe = " << oe << endl; 
    

    Output:

    ==== EXPERIMENT 1 === Print optional value
    oa = None[Double]
    ob = None[Double]
    oc = Some[Double](10.233)
    od = Some[Double](1000)
    oe = None[Double]
    
    • Experiment 2: Reset optional values
    std::puts("==== EXPERIMENT 2 === Reset optional value");
    std::cout << "Reset variable oc " << endl;
    oc.reset();
    std::cout << "oc = " << oc << endl;
    

    Output:

    ==== EXPERIMENT 2 === Reset optional value
    Reset variable oc 
    oc = None[Double]
    
    • Experiment 3: Check whether optional has value. (is not empty)
    std::puts("==== EXPERIMENT 3 ==== check whether optional has value. (is not empty) ====");
    if(oc)
       std::cout << ">> oc has a vaule" << endl;
    else
       std::cout << ">> oc is empty" << endl;
    

    Output:

    ==== EXPERIMENT 3 ==== check whether optional has value. (is not empty) ====
    >> oc is empty
    
    • Experiment 4: Try to parse double.
    std::puts("==== EXPERIMENT 4 ==== try parse double ====");      
    std::cout << "x0 = " << parseDouble("-1.0e3") << endl;
    std::cout << "x1 = " << parseDouble("1.351 ") << endl;
    std::cout << "x2 = " << parseDouble("1.asdasd351 xxgh") << endl;
    std::cout << "x4 = " << parseDouble("sad4543fgx") << endl;
    

    Output:

    ==== EXPERIMENT 4 ==== try parse double ====
    x0 = Some[Double](-1000)
    x1 = Some[Double](1.351)
    x2 = Some[Double](1)
    x4 = None[Double]
    
    • Experiment 5: Struct/class with optional field.
    std::puts("=== EXPERIMENT 5 ======== Structs/Classes with optional fields ====");
    std::vector<Location> xs {
        {10.31, 23.4, "Waypoint Delta"},
        {-46.23, 145.13, "Waypoint gamma"},
        {90.43, 100.345, std::nullopt},
        {0, 0, {}},
    };
    int i = 0;
    for(auto x: xs){
       std::cout << "x[" << i << "]" << " = " << x << endl;
       i++;
    }
    

    Output:

    === EXPERIMENT 5 ======== Structs/Classes with optional fields ====
    x[0] = Location(x = 10.31 ; y = 23.40 ; name = Waypoint Delta)
    x[1] = Location(x = -46.23 ; y = 145.13 ; name = Waypoint gamma)
    x[2] = Location(x = 90.43 ; y = 100.34 ; name = <unknown>)
    x[3] = Location(x = 0.00 ; y = 0.00 ; name = <unknown>)
    
    
    • Experiment 6: Print environment variables.
    std::puts("=== EXPERIMENT 6 - Print environment variables. ===== ");
    auto var_home =  getEnvVar("HOME");
    if(var_home.has_value())
        std::cout << "$HOME = " << var_home.value() << endl;
    else
        std::cout << "$HOME = " << "UNKNOWN" << endl;
    
    auto var_dummy =  getEnvVar("DUMMY-VAR");
    if(var_dummy.has_value())
        std::cout << "$DUMMYVAR = " << var_dummy.value() << endl;
    else
        std::cout << "$DUMMYVAR = " << "UNKNOWN" << endl;
    

    Output:

    === EXPERIMENT 6 - Print environment variables. ===== 
    $HOME = /home/archbox
    $DUMMYVAR = UNKNOWN
    
    • Experiment 6: Test member function .value_or()
    std::puts("==== EXPERIMENT 7 - Test member function .value_or() ====");
    std::cout << "$XDG_SESSION_PATH = "
              << getEnvVar("XDG_SESSION_PATH").value_or("<<error: not set>>") << '\n';
    std::cout << "$HOMEX = "
              << getEnvVar("HOMEX").value_or("<<error: not set>>") << '\n';
    
    return 0;
    

    Output:

    === EXPERIMENT 6 - Print environment variables. ===== 
    $HOME = /home/archbox
    $DUMMYVAR = UNKNOWN
    ==== EXPERIMENT 7 - Test member function .value_or() ====
    $XDG_SESSION_PATH = <<error: not set>>
    $HOMEX = <<error: not set>>
    

1.2.6 std::variant

C++17 new std::variant which comes from Boost.Variant provides a type-safe discriminated union or sum type which is similar to pattern matching from functional programming languages like Haskell, OCaml and Scala. In addition to those benefits, the std::variant is an out-of-the-box generic visitor design pattern and a type-safe replacement for old C-unions.

Potential Applications:

  • Implement visitor OOP pattern.
  • Simulate or emulate pattern matching from functional languages.
  • Manipulate abstract syntax trees.

Useful concepts references:

Documentation:

Code example:

#include <iostream>
#include <variant> // C++17
#include <string>
#include <ostream>
#include <deque>
#include <vector>
#include <iomanip>

template <class T>
auto display(const std::string& name, const T& t) -> void;

// Pattern matching using constexpr => May be the more performant way 
template<class T>
auto identifyAndPrint(const T& v) -> void;

struct VisitorOperation{
        auto operator()(int num) -> void {
                std::cout << "type = int     => value = " << num << "\n";
        }
        auto operator()(double num) -> void {
                std::cout << "type = double  => value = " << num << "\n";
        }
        auto operator()(const std::string& s){
                std::cout << "type = string  => value = " << s << "\n";
        }
};

int main(){
        // using <1>, <2>, ... <n> => Only available at C++17
        using std::cout, std::endl, std::cerr;
        auto nl = "\n";
        std::cout << std::boolalpha;
        cout << "========== Test 1 ==================" << nl;   

        // std::variant<int, double, std::string> somevar;
        auto x = std::variant<int, double, std::string>();
        x = 100;
        std::cout << "variant has int    = " << std::holds_alternative<int>(x) << nl;
        std::cout << "variant has double = " << std::holds_alternative<double>(x) << nl;
        std::cout << "variant has string = " << std::holds_alternative<std::string>(x) << nl;
        display("x", x);
        std::cout << "-------------------" << nl;
        x = 204.45;
        std::cout << "variant has double = " << std::holds_alternative<double>(x) << nl;    
        display("x", x);
        std::cout << "-------------------" << nl;
        x = "std::variant is awesome!";
        std::cout << "variant has string = " << std::holds_alternative<std::string>(x) << nl;
        display("x", x);

        cout << "========== Test 2 ==================" << nl;
        try{ 
                // Try to get int 
                int m = std::get<int>(x);
                std::cout << "m = " << m << "\n";
        } catch(const std::bad_variant_access& ex){
                std::cerr << "Error: Failed to extract int." << nl;
        }
        try{ 
                // Try to get string 
                auto s = std::get<std::string>(x);
                std::cout << "s = " << s << nl;
        } catch(const std::bad_variant_access& ex){
                std::cerr << "Error: Failed to extract string." << nl;
        }

        cout << "========== Test 3 ==================" << nl;
        x = -100;
        std::visit([](auto&& p){
                                   std::cout << "x = " << p << '\n';
                           }, x);
        x = 20.52;
        std::visit([](auto&& p){
                                   std::cout << "x = " << p << '\n';
                           }, x);

        x = "<hello world std::variant>";
        std::visit([](auto&& p){
                                   std::cout << "x = " << p << '\n';
                           }, x);

        cout << "========== Test 4 ==================" << nl;
        // auto + uniform initialization 
        auto xs = std::deque<std::variant<int, double, std::string>>{10.0, 20, 5, "hello", 10, "world"};
        for(const auto& e: xs){
                identifyAndPrint(e);
        }
        cout << "========== Test 5 ==================" << nl;
        for(const auto& e: xs){
                std::visit(VisitorOperation(), e);
        }   
        return 0;
}

// It works in a similar fashion to functional languages with
// pattern matching such as Haskell, Scala, OCaml and so on.
// std::variant is also a type-safe alternative to old C-unions.
template <class T>
auto display(const std::string& name, const T& t) -> void {
        auto nl = "\n";
        // Boost.Variant uses boost::get<TYPE>(&t), now changed to std::get_if
        if(auto n = std::get_if<int>(&t)){
                std::cout << " = " << *n << nl;
                return; // Early return 
        }
        if(auto d = std::get_if<double>(&t)){
                std::cout << name << " = " << *d << nl;
                return;
        }   
        if(auto s = std::get_if<std::string>(&t)){
                std::cout << name << " = " << *s << nl;
                return;
        }
        std::cout << "<UNKNOWN>" << std::endl;
}

template<class T>
auto identifyAndPrint(const T& v) -> void{
        std::visit([](auto&& a){
            using C = std::decay_t<decltype(a)>;
            if constexpr(std::is_same_v<C, int>){
                            std::cout << "Type is int => value = " << a << "\n";
                            return;
            }
            if constexpr(std::is_same_v<C, double>){
                            std::cout << "Type is double => value = " << a << "\n";
                            return;
            }
            if constexpr(std::is_same_v<C, std::string>){
                            std::cout << "Type is string => value = " << a << "\n";
                            return;
            }
            std::cout << "Type is unknown" << "\n";
                           }, v);
} // End of func. identifyAndPrint() ---//

Compile with GCC:

$ g++ variant.cpp -o variant.bin -std=c++1z -Wall -Wextra  && ./variant.bin 

Compile with Clang:

$ g++ variant.cpp -o variant.bin -std=c++1z -Wall -Wextra  && ./variant.bin 

Running:

g++ variant.cpp -o variant.bin -std=c++1z -Wall -Wextra  && ./variant.bin

========== Test 1 ==================
variant has int    = true
variant has double = false
variant has string = false
 = 100
-------------------
variant has double = true
x = 204.45
-------------------
variant has string = true
x = std::variant is awesome!
========== Test 2 ==================
Error: Failed to extract int.
s = std::variant is awesome!
========== Test 3 ==================
x = -100
x = 20.52
x = <hello world std::variant>
========== Test 4 ==================
Type is double => value = 10
Type is int => value = 20
Type is int => value = 5
Type is string => value = hello
Type is int => value = 10
Type is string => value = world
========== Test 5 ==================
type = double  => value = 10
type = int     => value = 20
type = int     => value = 5
type = string  => value = hello
type = int     => value = 10
type = string  => value = world


1.2.7 File system library

The C++17 File system library, based on Boost file systems, provides platform-agnostic file system operation such as listing directories, checking file permissions, creating directories, copying files and so on.

Small example about the library functionality

Source:

Compiling and running:

  • Note: it is necessary to link against stdc++fs.
$ g++ cpp17-filesys.cpp -o cpp17-filesys.bin -std=c++1z -O3 -Wall -Wextra -lstdc++fs  
$ ./cpp17-filesys.bin

Used headers and namespaces:

#include <iostream>
#include <string>
#include <iterator>
#include <iomanip>

// C++17 - Requires compiler linking flag: -lstdc++fs on CLang or GCC.
#include <filesystem>

namespace fs = std::filesystem;

Helper template function for applying a function to the first N entries.

/** Iterate over first N entries of a file system iterator. */
template<typename Range, typename Function>
auto dotimes(size_t n, Range&& iterable, Function fun){
        size_t i = 0;
        auto it = fs::begin(iterable);
        auto end = fs::end(iterable);
        while(i < n && it != end ){
                fun(it);
                ++it;
                i++;
        }
}

Main Function

  • Experiment 1:
std::cout << std::boolalpha;
std::cout << "\n EXPERIMENT 1 ===== Checking files in the system." << std::endl;
fs::path p1 = "/etc/iscsi/initiatorname.iscsi";
std::cout << "          p1 = " << p1 << std::endl;
std::cout << "p1.string()  = " << p1.string() << std::endl;
std::cout << "p1 ? exists  = " << fs::exists(p1) << std::endl;
std::cout << "p1 ? is File = " << fs::is_regular_file(p1) << std::endl;
std::cout << "p1 ? is Dir  = " << fs::is_directory(p1) << std::endl;

fs::path p2 = "/boot";
std::cout << "          p2 = " << p2 << std::endl;
std::cout << "p2.string()  = " << p2.string() << std::endl;
std::cout << "p2 ? exists  = " << fs::exists(p2) << std::endl;
std::cout << "p2 ? is File = " << fs::is_regular_file(p2) << std::endl;
std::cout << "p2 ? is Dir  = " << fs::is_directory(p2) << std::endl;

fs::path p3 = "/boot/does/not/exist";
std::cout << "          p3 = " << p3 << std::endl;
std::cout << "p3.string()  = " << p3.string() << std::endl;
std::cout << "p3 ? exists  = " << fs::exists(p3) << std::endl;
std::cout << "p3 ? is File = " << fs::is_regular_file(p3) << std::endl;
std::cout << "p3 ? is Dir  = " << fs::is_directory(p3) << std::endl;

Output:

 EXPERIMENT 1 ===== Checking files in the system.
          p1 = "/etc/iscsi/initiatorname.iscsi"
p1.string()  = /etc/iscsi/initiatorname.iscsi
p1 ? exists  = true
p1 ? is File = true
p1 ? is Dir  = false
          p2 = "/boot"
p2.string()  = /boot
p2 ? exists  = true
p2 ? is File = false
p2 ? is Dir  = true
          p3 = "/boot/does/not/exist"
p3.string()  = /boot/does/not/exist
p3 ? exists  = false
p3 ? is File = false
p3 ? is Dir  = false

  • Experiment 2:
std::cout << "\n EXPERIMENT 2 ===== Listing directory /etc =====" << std::endl;
// Show first 10 files of directory /etc 
dotimes(10, fs::directory_iterator("/etc"),
            [](auto p){
                auto path = p->path();
                std::cout << std::left
                          << std::setw(0) << path.filename().string()
                          << " " << std::setw(35)
                          << std::right << std::setw(40) << path
                          << std::endl;             
            });

Output:

 EXPERIMENT 2 ===== Listing directory /etc =====
chkconfig.d                       "/etc/chkconfig.d"
DIR_COLORS.lightbgcolor           "/etc/DIR_COLORS.lightbgcolor"
crypttab                          "/etc/crypttab"
iscsi                             "/etc/iscsi"
depmod.d                          "/etc/depmod.d"
vbox                              "/etc/vbox"
rhashrc                           "/etc/rhashrc"
issue.net                         "/etc/issue.net"
java                              "/etc/java"
authselect                        "/etc/authselect"
  • Experiment 3:
std::cout << "\n EXPERIMENT 3 = Listing directory /etc (recursive) =====" << std::endl;
dotimes(20, fs::recursive_directory_iterator("/etc/"),
                [](auto p){
                    std::cout << std::right
                              << std::setw(10)  << fs::is_directory(p->path())
                              << std::setw(10)  << fs::is_regular_file(p->path())
                              << std::setw(10)  << fs::is_symlink(p->path())
                              << std::setw(10)  << " "
                              << std::setw(5)   << std::left << p->path()                     
                              << std::endl;
                });

Output:

  • 1st colum => Directory
  • 2nd column => File
  • 3rd column => Symbolic link
  • 4th column => Absolute file path.
EXPERIMENT 3 = Listing directory /etc (recursive) =====
     true     false     false          "/etc/chkconfig.d"
    false      true     false          "/etc/DIR_COLORS.lightbgcolor"
    false      true     false          "/etc/crypttab"
     true     false     false          "/etc/iscsi"
    false      true     false          "/etc/iscsi/iscsid.conf"
    false      true     false          "/etc/iscsi/initiatorname.iscsi"
     true     false     false          "/etc/depmod.d"
     true     false     false          "/etc/vbox"
    false      true     false          "/etc/vbox/vbox.cfg"
    false      true     false          "/etc/rhashrc"
    false      true      true          "/etc/issue.net"
     true     false     false          "/etc/java"
    false      true     false          "/etc/java/font.properties"
    false      true     false          "/etc/java/java.conf"

Documentation:

1.3 C++20 New Features

1.3.1 C++20 Compiler Support

1.3.2 std::endian - Detect processor endianess

File: cpp20_endian.cpp

#include <iostream> 
#include <string>

// C++20 headers
#include <bit> 

int main()
{
     std::cout << std::boolalpha;

     std::cout << " => Processor is big endian?: "
               <<  (std::endian::native == std::endian::big) <<  "\n";

     std::cout << " => Processor is little endian?: "
               <<  (std::endian::native == std::endian::little) <<  "\n";

     return 0;
}

Building and running:

# Clang version 8 
$ clang++ cpp20a.cpp -o cpp20a.bin -std=c++2a  -Wall -Wextra -pedantic -stdlib=libc++

$ ./cpp20a.bin
 => Processor is big endian?: false
 => Processor is little endian?: true

1.3.3 Designated initialization

Allows to write constructors in a self-documenting way:

File: designated_initialization.cpp

#include <iostream> 
#include <string>

struct Point2D{
     float x;
     float y;

     friend std::ostream& operator<<(std::ostream& os, Point2D const& p)
     {
        return os << " Point{ x = " << p.x << " ; y = " << p.y << " } " << std::endl;
     }
};

class Product
{
public:
    Product(int id, std::string name, double price)
            : m_id(id), m_name(name), m_price(price)
    {       
    }

    int         id()    const { return m_id; }
    double      price() const { return m_price; }
    std::string name()  const { return m_name; }

private:
    int         m_id;
    std::string m_name;
    double      m_price;
};


int main()
{
    std::cout << std::boolalpha;

    Point2D p1 { .x = 2.5f, .y = -10.20f};
    std::cout << " =>> p1 = " << p1 << "\n";

    Point2D p2 = { .x = -12.55f, .y = -3.5f};
    std::cout << " =>> p2 = " << p2 << "\n";    

    auto p3 = Point2D{ .x = -10.55f, .y = 13.5f};
    std::cout << " =>> p3 = " << p3 << "\n";    

    Product prod{ .id = 200, .name = "Colombian coffee", .price = 100.56};
    std::cout << " Product => id = " << prod.id()
              << " ; name = " << prod.name()
              << " ; price = " << prod.price()
              << "\n";

    return 0;
}

Building and running:

  • Note: built with GCC 8.3.1
$ ./out.bin
 =>> p1 =  Point{ x = 2.5 ; y = -10.2 }

 =>> p2 =  Point{ x = -12.55 ; y = -3.5 }

 =>> p3 =  Point{ x = -10.55 ; y = 13.5 }

 Product => id = 200 ; name = Colombian coffee ; price = 100.56

1.3.4 Format Utility Library

The C++20 format library, modeled after fmtlib, allows formatting strings in a extensible, concise and type-safe way in a similar fashion to C-printf and Python text formatting features. Despite the C++20 format utility library benefits, most C++ compilers still do not implement this library yet and not all features of fmtlib were ported to C++20.

Note: This experiment was carried out on MSVC comiler 19.29.30037 on a Windows 10 QEMU Virtual Machine running on a Linux host.

References:

Fmtlib (for comparison)

Sample code

File: cpp20-format.cpp

#include <iostream>
#include <string> 
#include <format>
#include <vector> 

int main()
{

  std::puts("\n=============== EXPERIMENT 1 ==========================");
  {
    std::cout << std::format(" true == {0} ; false == {1} \n", true, false)
              << std::endl;
  }


  std::puts("\n=============== EXPERIMENT 2 / Numeric base ==========\n");
  {

     auto fm = std::format(" [BASES] => dec: {0:d} ; hex = 0x{0:X} "
                           " ; oct = {0:o} ; bin = 0b{0:b} \n", 241 );
     std::cout << fm << std::endl;
  }

  std::puts("\n============== EXPERIMENT 3 / Numeric output ===========\n");
  {

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

    x = 28524.0;

    std::cout << std::format(" log(x) = {:.2F}  (2 digit precision)\n", std::log(x));
    std::cout << std::format(" log(x) = {:+.6F} (6 digit precision)\n", std::log(x));
    std::cout << std::format(" 2000 * log(x) = {:+.6G} (6 digit precision)\n", 1e5 * std::log(x));

    std::cout << std::format("  log(x) = {0:+.8E} ; sqrt(x) = {1:+8E} (8 digit precision)\n",
                             std::log(x), std::sqrt(x));

  }

  std::puts("\n============ EXPERIMENT 4 - Print numeric table ==================");
  {

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

  return 0;
}

Building and running

Building:

$ %comspec%  /k "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars64.bat"
$ cl.exe cpp20-format.cpp /std:c++latest /EHsc /Zi /DEBUG

Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30037 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

/std:c++latest is provided as a preview of language features from the latest C++
working draft, and we're eager to hear about bugs and suggestions for improvements.
However, note that these features are provided as-is without support, and subject
to changes or removal as the working draft evolves. See
https://go.microsoft.com/fwlink/?linkid=2045807 for details.

Running:

$ .\cpp20-format.exe

=============== EXPERIMENT 1 ==========================
 true == true ; false == false


=============== EXPERIMENT 2 / Numeric base ==========

 [BASES] => dec: 241 ; hex = 0xF1  ; oct = 361 ; bin = 0b11110001


============== EXPERIMENT 3 / Numeric output ===========

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 4 - 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

1.3.5 Ranges Library / Algorithms

Documentation:

Files

File: ranges-algorithms.cpp

#include <iostream>
#include <iostream>
#include <vector>
#include <deque>

#include <algorithm>
#include <ranges>


template<typename Element>
std::ostream&
operator<<(std::ostream& os, std::vector<Element> const& xs )
{
    os << "[" << xs.size() << "]( ";
    for(const auto&  x: xs)
    os << x << " ";
    return os << " )";
}

template<typename Element>
std::ostream&
operator<<(std::ostream& os, std::deque<Element> const& xs )
{
    os << "[" << xs.size() << "]( ";
    for(const auto&  x: xs)
    os << x << " ";
    return os << " )";
}



int main()
{

    // ---------------------------------------- //

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


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

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

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

    std::puts("\n === EXPERIMENT 1 B - for_each ===");
    {
        std::string astr = "bolts";
        std::ranges::for_each(astr, printLambda);
    }

    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;
        std::ranges::sort(xs2);
        std::cout << " AFTER xs2 = " << xs2 << std::endl;
    }


    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;

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

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


    std::puts("\n === EXPERIMENT 4 - reverse ===");
    {
        std::deque<std::string> words = {"c++", "c++17", "C++20", "asm", "ADA"};
        std::cout << " BEFORE => words = " << words << std::endl;
        std::ranges::reverse(words);
        std::cout << " AFTER => words = " << words << std::endl;
    }

    std::puts("\n === EXPERIMENT 6 - find ===");
    {
        std::vector<int> xvec = {1, 2, 5, 10, 1, 4, 1, 2};

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

        }
    }



}

Building:

$ g++ -std=c++20 -O2 -Wall -pedantic main.cpp && ./a.out

Program output: (GCC 11 - coliru - online C++20 compiler )

=== EXPERIMENT 1 A - for_each ===
=> Print numbers
x = 8
x = 9
x = 20
x = 25
=> Print words
x = c++
x = c++17
x = C++20
x = asm
x = ADA

=== EXPERIMENT 1 B - for_each ===
x = b
x = o
x = l
x = t
x = s

=== 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 - fill ===
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 - reverse ===
BEFORE => words = [5]( c++ c++17 C++20 asm ADA  )
AFTER => words = [5]( ADA asm C++20 c++17 c++  )

=== EXPERIMENT 6 - find ===
[OK] Found value == 10 => *it = 10
Position = 3

1.3.6 Ranges Library / Views

File: views.cpp

#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>

namespace views = std::ranges::views;

template<typename Element>
std::ostream&
operator<<(std::ostream& os, std::vector<Element> const& xs )
{
    os << "[" << xs.size() << "]( ";
    for(const auto&  x: xs)
       os << x << " ";
    return os << " )";
}

int main()
{
  auto data = std::vector<int>{ -40, 20, 100, -80, 100, -5, 2, 73, 19, 11, 125};

  // Note: Those computations are performed lazily
  auto result = data | views::transform([](int x){ return 3 * x + 25; })
                     | views::filter([](int x){ return x >= 0; })
                     | views::drop(3)
                     | views::reverse
                     | views::filter([](int x){ return x >= 0; })
                     ;

  std::cout << "\n --- C++ Ranges - Iterating over view -----------" << std::endl;

  int i = 0;

  for(auto k: result)
  {
      std::cout << " result[" << i++ << "] = " << k << std::endl;
  }

  std::cout << "\n --- C++ Ranges - Copying a view to a vector -------" << std::endl;

  auto result_vector =  std::vector(result.begin(), result.end());
  std::cout << "result_vector = " << result_vector << std::endl;

  return 0;
}

Building with GCC-11 docker:

$ docker run --rm --interactive --tty --workdir /cwd --volume=$PWD:/cwd gcc:latest
$ g++ views.cpp -o views.bin -std=c++20 -Wall -Wextra -g 

Running:

$ ./views.bin 
--- C++ Ranges - Iterating over view -----------
result[0] = 400
result[1] = 58
result[2] = 82
result[3] = 244
result[4] = 31
result[5] = 10

--- C++ Ranges - Copying a view to a vector -------
result_vector = [6]( 400 58 82 244 31 10  )

1.3.7 std::span library

The C++20 templated class std::span is a non-owning view of a contiguous sequence of objects, such as buffers represented by pointer and size, arrays, static allocated arrays, heap-allocated arrays, std::array, std::vector and std::string. This class wraps a pointer to the first sequence element and sequence size. Non-owning means that the std::span neither allocates nor releases memory related to the sequence pointed by the class.

Benefits and observations:

  • A function that takes a std::span as parameter can accept std::string, std::vector, std::array, pointer-size pair (buffers) and C-style arrays without any code modification or creating more overloading functions for each of the stated types.
  • Avoid bugs related to C-style arrays and C-style buffers that are originated rom arrays decaying to pointers.

Documentation:

Other implementations:

Example code

File: main.cpp (Online Compiler)

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

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

void display_sum(const char* name, std::span<int> xs)
{
    auto result = std::accumulate(xs.begin(), xs.end(), 0);
    std::cout << " [SUM] sum(" << name << ") = " << result << std::endl;
}

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 main()
{


    std::puts("\n ==== EXPERIMENT 1 - std::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
        std::span<int> sp;

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

        int* ptr_data = sp.data();
        display_sum("carray", carray);
        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 = std::span(buffer_ptr, buffer_size);
        display_span("buffer", sp);

        std::cout << std::endl;

        std::vector<int> vector1 = {10, 256, -15, 20, -8};
        sp = vector1; // View refers to vector1
        display_sum("vector1", 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_sum("std_array", std_array);
        display_span("std_array [A]", sp);
        display_span("std_array [B]", sp);
    }

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

     {
         int carrayA [] = {10, 15, -8, 251, 56, 15, 100};
         auto sp = std::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;

     }


    return 0;
}

Building:

$ g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp && ./a.out

Running:

==== EXPERIMENT 1 - std::span variable =====================

[SUM] sum(carray) = 49
=> 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,  ]

[SUM] sum(vector1) = 263
=> vector1 [A] = [ 10, 256, -15, 20, -8,  ]
=> vector1 [B] = [ 10, 256, -15, 20, -8,  ]
[SUM] sum(std_array) = 374
=> std_array [A] = [ 100, -56, 6, 87, 61, 25, 151,  ]
=> std_array [B] = [ 100, -56, 6, 87, 61, 25, 151,  ]

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

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

==== EXPERIMENT 3 - std::span methods ==================

=>> sp.empty()   = false
=>>  sp.size()   =     7
=>> sp.size_byte()   =    28
=>>      sp[0]   =    10
=>>      sp[4]   =    56
=>>      sp[5]   =    15

Created: 2021-09-22 Wed 11:25

Validate