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
- 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::variant<std::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:
1.1.3 C++20 Standard
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:
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
- Algorithm std::clamp
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; }
- 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 }
- 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
- File: 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
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:
- 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.
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
- 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:
- 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;
- Example
Source:
- File: 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>>
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:
- File: 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.)
1.3 C++20 New Features
1.3.1 C++20 Compiler Support
- 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
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:
- 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