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.
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
- Boost.Optional - boost library
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::variantstd::monostate can be used instead).”
- Reference Implementation:
- Boost.Variant - boost library
Parallel Algorithms of C++17
- Note: Most compilers does not provide this part of standard library yet, only Intel compiler is shipped with this library.
- Standard Library Reference:
- Reference Implementation:
- https://github.com/intel/parallelstl
- “Parallel STL is an implementation of the C++ standard library algorithms with support for execution policies, as specified in ISO/IEC 14882:2017 standard, commonly called C++17. The implementation also supports the unsequenced execution policy specified in Parallelism TS version 2 and proposed for the next version of the C++ standard in the C++ working group paper P1001R1. Parallel STL offers a portable implementation of threaded and vectorized execution of standard C++ algorithms, optimized and validated for Intel(R) 64 processors. For sequential execution, it relies on an available implementation of the C++ standard library.”
- Articles:
C++20 std::span
- Standard Library Reference:
- https://en.cppreference.com/w/cpp/container/span
- “The class template span describes an object that can refer to a contiguous sequence of objects with the first element of the sequence at position zero. A span can either have a static extent, in which case the number of elements in the sequence is known and encoded in the type, or a dynamic extent. A typical implementation holds only two members: a pointer to T and a size.”
- Reference Implementation:
- Alternative Implementation:
- Papers:
- P0122R33 - span: bounds-safe views for sequences
- P0298R1 - A byte type definition
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
- Note: This library provide a better type-safe printf API, which is based on Python.
- Standard Library Reference:
- https://en.cppreference.com/w/cpp/utility/format
- https://en.cppreference.com/w/cpp/utility/format
- “The text formatting library offers a safe and extensible alternative to the printf family of functions. It is intended to complement the existing C++ I/O streams library and reuse some of its infrastructure such as overloaded insertion operators for user-defined types.”
- Reference Implementation:
C++20 jthread
- Standard Library Reference
- thread/jthread - CppReference
- Reference Implementation:
- https://github.com/josuttis/jthread
- “C++ class for a joining and cooperative interruptible thread (should become std::jthread). C++ class for a joining and cooperative interruptible thread (should become std::jthread) with stop_token helper. Draft implementation of the C++ paper P0660 https://wg21.link/p0660”
- Papers:
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 }
Clamps the the input value to range [LOW, HIGH]:
- Header: <algorithm>
- Documentation: std::clamp
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;
}
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 }
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
- File: src/cpp17/cpp17-invoke.cpp
- Online Compiler: http://rextester.com/IPY88297
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
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:
- attribute specifier sequence(since C++11) (Cpp reference)
- Attributes in C++ | Microsoft Docs
- Clang Language Extensions — Clang 10 documentation
- WG21 Paper - Towards supporrt for attributes in C++ (Revision 6)
- http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2761.pdf
- C++ Proposal for attributes language extension. One of the main cases presented for this proposal is the easier usage of OpenMP language extension pragmas.
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: 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
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:
- std::optional::optional - cppreference.com
- std::optional::value_or - cppreference.com
- Boost.Optional (Predecessor of std::optional)
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;
Source:
- File: src/cpp17/optional1.cpp
- GIST: optional1.cpp
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>>
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:
- File: src/cpp17/variant.cpp
#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
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:
- File: src/cpp17/cpp17-filesys1.cpp
- GIST: cpp17-filesys1.cpp
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:
- Filesystem library - cppreference.com
- C++17 Filesystem -
- Boost Filesystem Tutorial (Note: it is not the std::filesystem library, but its predecessor, however it is worth reading it.)
- https://en.cppreference.com/w/cpp/compiler_support
- Note: Not all C++20 features are supported by all compilers yet.
- 2019-07 Cologne ISO C++ Committee Trip Report
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
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
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
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
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 )
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:
- gsl::span (GSL - Library)
- absl::span (Google’s abseil library)
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