Modern C++
Programming
16. Utilities
Federico Busato
2024-03-29
Table of Contents
1 I/O Stream
Manipulator
ofstream/ifstream
2 Strings and std::print
std::string
Conversion from/to Numeric Values
std::string view
std::format
std::print
1/84
Table of Contents
3 View
std::span
4 Math Libraries
5 Random Number
Basic Concepts
C++ <random>
Seed
PRNG Period and Quality
Distribution
Quasi-random
2/84
Table of Contents
6 Time Measuring
Wall-Clock Time
User Time
System Time
3/84
Table of Contents
7 Std Classes
std::pair
std::tuple
std::variant
std::optional
std::any
std::stacktrace
8 Filesystem Library
Query Methods
Modify Methods
4/84
I/O Stream
I/O Stream
<iostream> input/output library refers to a family of classes and supporting
functions in the C++ Standard Library that implement stream-based input/output
capabilities
There are four predeﬁned iostreams:
• cin standard input (stdin)
• cout standard output (stdout) [buﬀered]
• cerr standard error (stderr) [unbuﬀered]
• clog standard error (stderr) [unbuﬀered]
buﬀered: the content of the buﬀer is not write to disk until some events occur
5/84
I/O Stream (manipulator) 1/3
Basic I/O Stream manipulator:
• flush ﬂushes the output stream cout ≪ flush;
• endl shortcut for cout ≪ "\n" ≪ flush;
cout ≪ endl
• flush and endl force the program to synchronize with the terminal → very
slow operation!
6/84
I/O Stream (manipulator) 2/3
• Set integral representation: default: dec
cout ≪ dec ≪ 0xF; prints 16
cout ≪ hex ≪ 16; prints 0xF
cout ≪ oct ≪ 8; prints 10
• Print the underlying bit representation of a value:
#include <bitset>
std::cout << std::bitset<32>(3.45f); // (32: num. of bits)
// print 01000000010111001100110011001101
• Print true/false text:
cout ≪ boolalpha ≪ 1; prints true
cout ≪ boolalpha ≪ 0; prints false
7/84
I/O Stream (manipulator) 3/3
<iomanip>
• Set decimal precision: default: 6
cout ≪ setprecision(2) ≪ 3.538; → 3.54
• Set ﬂoat representation: default: std::defaultfloat
cout ≪ setprecision(2) ≪ fixed ≪ 32.5; → 32.50
cout ≪ setprecision(2) ≪ scientific ≪ 32.5; → 3.25e+01
• Set alignment: default: right
cout ≪ right ≪ setw(7) ≪ "abc" ≪ "##"; → abc##
cout ≪ left ≪ setw(7) ≪ "abc" ≪ "##"; → abc ##
(better than using tab \t)
8/84
I/O Stream - std::cin
std::cin is an example of input stream. Data coming from a source is read by the program.
In this example cin is the standard input
#include <iostream>
int main() {
int a;
std::cout << "Please enter an integer value:" << endl;
std::cin >> a;
int b;
float c;
std::cout << "Please enter an integer value "
<< "followed by a float value:" << endl;
std::cin >> b >> c; // read an integer and store into "b",
} // then read a float value, and store
// into "c"
9/84
I/O Stream - ofstream/ifstream 1/3
ifstream , ofstream are output and input stream too
<fstream>
• Open a ﬁle for reading
Open a ﬁle in input mode: ifstream my file("example.txt")
• Open a ﬁle for writing
Open a ﬁle in output mode: ofstream my file("example.txt")
Open a ﬁle in append mode: ofstream my file("example.txt", ios::out | ios::app)
• Read a line getline(my file, string)
• Close a ﬁle my file.close()
• Check the stream integrity my file.good()
10/84
I/O Stream - ofstream/ifstream 2/3
• Peek the next character
char current char = my file.peek()
• Get the next character (and advance)
char current char = my file.get()
• Get the position of the current character in the input stream
int byte offset = my file.tellg()
• Set the char position in the input sequence
my file.seekg(byte offset) (absolute position)
my file.seekg(byte offset, position) (relative position)
where position can be:
ios::beg (the begin), ios::end (the end),
ios::cur (current position)
11/84
I/O Stream - ofstream/ifstream 3/3
• Ignore characters until the delimiter is found
my file.ignore(max stream size, <delim>)
e.g. skip until end of line \n
• Get a pointer to the stream buﬀer object currently associated with the stream
my file.rdbuf()
can be used to redirect ﬁle stream
12/84
I/O Stream - Example 1
Open a ﬁle and print line by line:
#include <iostream>
#include <fstream>
int main() {
std::ifstream fin("example.txt");
std::string str;
while (std::getline(fin, str))
std::cout << str << "\n";
fin.close();
}
An alternative version with redirection:
# include <iostream>
# include <fstream>
int main() {
std::ifstream fin("example.txt");
std::cout << fin.rdbuf();
fin.close();
}
Reading files line by line in C++ using ifstream
13/84
I/O Stream - Example 2
example.txt:
23 70 44\n
\t57\t89
The input stream is independent from the
type of space (multiple space, tab, new-
line \n, \r\n, etc.)
Another example:
#include <iostream>
#include <fstream>
int main() {
std::ifstream fin("example.txt");
char c = fin.peek(); // c = '2'
while (fin.good()) {
int var;
fin >> var;
std::cout << var;
} // print 2370445789
fin.seekg(4);
c = fin.peek(); // c = '0'
fin.close();
}
14/84
I/O Stream -Check the End of a File
• Check the current character
while (fin.peek() != std::char_traits<char>::eof()) // C: EOF
fin >> var;
• Check if the read operation fails
while (fin >> var)
...
• Check if the stream past the end of the ﬁle
while (true) {
fin >> var
if (fin.eof())
break;
}
15/84
I/O Stream (checkRegularType)
Check if a ﬁle is a regular ﬁle and can be read/written
#include <sys/types.h>
#include <sys/stat.h>
bool checkRegularFile(const char* file_path) {
struct stat info;
if (::stat( file_path, &info ) != 0)
return false; // unable to access
if (info.st_mode & S_IFDIR)
return false; // is a directory
std::ifstream fin(file_path); // additional checking
if (!fin.is_open() || !fin.good())
return false;
try { // try to read
char c; fin >> c;
} catch (std::ios_base::failure&) {
return false;
}
return true;
}
16/84
I/O Stream - File size
Get the ﬁle size in bytes in a portable way:
long long int fileSize(const char* file_path) {
std::ifstream fin(file_path); // open the file
fin.seekg(0, ios::beg); // move to the first byte
std::istream::pos_type start_pos = fin.tellg();
// get the start offset
fin.seekg(0, ios::end); // move to the last byte
std::istream::pos_type end_pos = fin.tellg();
// get the end offset
return end_pos - start_pos; // position difference
}
see C++17 ﬁle system utilities
17/84
Strings and std::print
std::string 1/4
std::string is a wrapper of character sequences
More ﬂexible and safer than raw char array but can be slower
#include <string>
int main() {
std::string a; // empty string
std::string b("first");
using namespace std::string_literals; // C++14
std::string c = "second"s; // C++14
}
std::string supports constexpr in C++20
18/84
std::string - Capacity and Search 2/4
• empty() returns true if the string is empty, false otherwise
• size() returns the number of characters in the string
• find(string) returns the position of the ﬁrst substring equal to the given character
sequence or npos if no substring is found
• rfind(string) returns the position of the last substring equal to the given character
sequence or npos if no substring is found
• find first of(char seq) returns the position of the ﬁrst character equal to one of the
characters in the given character sequence or npos if no characters is found
• find last of(char seq) returns the position of the last character equal to one of the
characters in the given character sequence or npos if no characters is found
npos special value returned by string methods
19/84
std::string - Operations 3/4
• new string substr(start pos)
returns a substring [start pos, end]
new string substr(start pos, count)
returns a substring [start pos, start pos + count)
• clear() removes all characters from the string
• erase(pos) removes the character at position
erase(start pos, count)
removes the characters at positions [start pos, start pos + count)
• replace(start pos, count, new string)
replaces the part of the string indicated by [start pos, start pos + count) with new string
• c str()
returns a pointer to the raw char sequence
20/84
std::string - Overloaded Operators 4/4
• access speciﬁed character string1[i]
• string copy string1 = string2
• string compare string1 == string2
works also with !=,<,≤,>,≥
• concatenate two strings string concat = string1 + string2
• append characters to the end string1 += string2
21/84
Conversion from/to Numeric Values
Converts a string to a numeric value C++11:
• stoi(string) string to signed integer
• stol(string) string to long signed integer
• stoul(string) string to long unsigned integer
• stoull(string) string to long long unsigned integer
• stof(string) string to ﬂoating point value (ﬂoat)
• stod(string) string to ﬂoating point value (double)
• stold(string) string to ﬂoating point value (long double)
• C++17 std::from chars(start, end, result, base) fast string conversion (no
allocation, no exception)
Converts a numeric value to a string:
• C++11 to string(numeric value) numeric value to string
22/84
Examples
std::string str("si vis pacem para bellum");
cout << str.size(); // print 24
cout << str.find("vis"); // print 3
cout << str.find_last_of("bla"); // print 21, 'l' found
cout << str.substr(7, 5);// print "pacem", pos=7 and count=5
cout << str[1]; // print 'i'
cout << (str == "vis"); // print false
cout << (str < "z"); // print true
const char* raw_str = str.c_str();
cout << string("a") + "b"; // print "ab"
cout << string("ab").erase(0); // print 'b'
char* str2 = "34";
int a = std::stoi(str2); // a = 34;
std::string str3 = std::to_string(a); // str3 = "34"
23/84
Tips
• Conversion from integer to char letter (e.g. 3 → 'C'):
static cast<char>('A'+ value)
value ∈ [0, 26] (English alphabet)
• Conversion from char to integer (e.g. ’C’ → 3): value - 'A'
value ∈ [0, 26]
• Conversion from digit to char number (e.g. 3 → '3'):
static cast<char>('0'+ value)
value ∈ [0, 9]
• char to string std::string(1, char value)
24/84
std::string view 1/3
C++17 std::string view describes a minimum common interface to interact with
string data:
• const std::string&
• const char*
The purpose of std::string view is to avoid copying data which is already owned
by the original object
#include <string>
#include <string_view>
std::string str = "abc"; // new memory allocation + copy
std::string_view = "abc"; // only the reference
25/84
std::string view 2/3
std::string view provides similar functionalities of std::string
#include <iostream>
#include <string>
#include <string_view>
void string_op1(const std::string& str) {}
void string_op2(std::string_view str) {}
string_op1("abcdef"); // allocation + copy
string_op2("abcdef"); // reference
const char* str1 = "abcdef";
std::string str2("abcdef"); // allocation + copy
std::cout << str2.substr(0, 3); // print "abc"
std::string_view str3(str1); // reference
std::cout << str3.substr(0, 3); // print "abc"
26/84
std::string view 3/3
std::string view supports constexpr constructor and methods
constexpr std::string_view str1("abc");
constexpr std::string_view str2 = "abc";
constexpr char c = str1[0]; // 'a'
constexpr bool b = (str1 == str2); // 'true'
constexpr int size = str1.size(); // '3'
constexpr std::string_view str3 = str1.substr(0, 2); // "ab"
constexpr int pos = str1.find("bc"); // '1'
27/84
std::format 1/2
printf functions: no automatic type deduction, error prone, not extensible
stream objects: very verbose, hard to optimize
C++20 std::format provides python style formatting:
• Type-safe
• Support positional arguments
• Extensible (support user-deﬁned types)
• Return a std::string
28/84
std::format - Example 2/2
Integer formatting
std::format("{}", 3); // "3"
std::format("{:b}", 3); // "101"
Floating point formatting
std::format("{:.1f}", 3.273); // "3.1"
Alignment
std::format("{:>6}", 3.27); // " 3.27"
std::format("{:<6}", 3.27); // "3.27 "
Argument reordering
std::format("{1} - {0}", 1, 3); // "3 - 1"
29/84
std::print
C++23 introduces std::print() std::println()
std::print("Hello, {}!\n", name);
std::println("Hello, {}!", name); // prints a newline
std::print in C++23
30/84
View
std::span 1/3
C++20 introduces std::span which is a non-owning view of an underlying sequence
or array
A std::span can either have a static extent, in which case the number of elements
in the sequence is known at compile-time, or a dynamic extent
template<
class T,
std::size_t Extent = std::dynamic_extent
> class span;
31/84
std::span 2/3
# include <span>
# include <array>
# include <vector>
int array1[] = {1, 2, 3};
std::span s1{array1}; // static extent
std::array<int, 3> array2 = {1, 2, 3};
std::span s2{array2}; // static extent
auto array3 = new int[3];
std::span s3{array3, 3}; // dynamic extent
std::vector<int> v{1, 2, 3};
std::span s4{v.data(), v.size()}; // dynamic extent
std::span s5{v}; // dynamic extent
32/84
std::span 3/3
void f(std::span<int> span) {
for (auto x : span) // range-based loop (safe)
cout << x;
std::fill(span.begin(), span.end(), 3); // std algorithms
}
int array1[] = {1, 2, 3};
f(array1);
auto array2 = new int[3];
f({array2, 3});
33/84
Math Libraries
<cmath> Math Library 1/2
<cmath>
• fabs(x) computes absolute value, |x|, C++11
• exp(x) returns e raised to the given power, e
x
• exp2(x) returns 2 raised to the given power, 2
x
, C++11
• log(x) computes natural (base e) logarithm, log
e
(x)
• log10(x) computes base 10 logarithm, log
10
(x)
• log2(x) computes base 2 logarithm, log
2
(x), C++11
• pow(x, y) raises a number to the given power, x
y
• sqrt(x) computes square root,
√
x
• cqrt(x) computes cubic root,
3
√
x, C++11
34/84
<cmath> Math Library 2/2
• sin(x) computes sine, sin(x)
• cos(x) computes cosine, cos(x)
• tan(x) computes tangent, tan(x)
• ceil(x) nearest integer not less than the given value, ⌈x⌉
• floor(x) nearest integer not greater than the given value, ⌊x⌋
• round|lround|llround(x) nearest integer,

x +
1
2

(return type: ﬂoating point, long, long long respectively)
Math functions in C++11 can be applied directly to integral types without implicit/explicit
casting (return type: ﬂoating point).
en.cppreference.com/w/cpp/numeric/math
35/84
<limits> Numerical Limits
Get numeric limits of a given type:
<limits> C++11
T numeric_limits<T>:: max() // returns the maximum finite value
// value representable
T numeric_limits<T>:: min() // returns the minimum finite value
// value representable
T numeric_limits<T>:: lowest() // returns the lowest finite
// value representable
36/84
<numeric> Mathematical Constants
<numeric> C++20
The header provides numeric constants
• e Euler number e
• pi π
• phi Golden ratio
1+
√
5
2
• sqrt2
√
2
37/84
Integer Division
Integer ceiling division and rounded division:
• Ceiling Division:

value
div

unsigned ceil_div(unsigned value, unsigned div) {
return (value + div - 1) / div;
} // note: may overflow
• Rounded Division:

value
div
+
1
2

unsigned round_div(unsigned value, unsigned div) {
return (value + div / 2) / div;
} // note: may overflow
Note: do not use ﬂoating-point conversion (see Basic Concept I)
38/84
Random Number
Random Number
“Random numbers should not be generated with a method chosen at random”
— Donald E. Knuth
Applications: cryptography, simulations (e.g. Monte Carlo), etc.
39/84
Random Number
see Lavarand
40/84
Basic Concepts
• A pseudorandom (PRNG) sequence of numbers satisﬁes most of the statistical
properties of a truly random sequence but is generated by a deterministic algorithm
(deterministic ﬁnite-state machine)
• A quasirandom sequence of n-dimensional points is generated by a deterministic
algorithm designed to ﬁll an n-dimensional space evenly
• The state of a PRNG describes the status of the generator (the values of its variables),
namely where the system is after a certain amount of transitions
• The seed is a value that initializes the starting state of a PRNG. The same seed always
produces the same sequence of results
• The oﬀset of a sequence is used to skip ahead in the sequence
• PRNGs produce uniformly distributed values. PRNGs can also generate values according
to a probability function (binomial, normal, etc.)
41/84
C++ <random> 1/2
The problem:
C rand() function produces poor quality random numbers
• C++14 discourage the use of rand() and srand()
C++11 introduces pseudo random number generation (PRNG) facilities to produce
random numbers by using combinations of generators and distributions
A random generator requires four steps:
(1) Select the seed
(2) Deﬁne the random engine
<type of random engine> generator(seed)
(3) Deﬁne the distribution
<type of distribution> distribution(range start, range end)
(4) Produce the random number
distribution(generator)
42/84
C++ <random> 2/2
Simplest example:
#include <iostream>
#include <random>
int main() {
unsigned seed = ...;
std::default_random_engine generator(seed);
std::uniform_int_distribution<int> distribution(0, 9);
std::cout << distribution(generator); // first random number
std::cout << distribution(generator); // second random number
}
It generates two random integer numbers in the range [0, 9] by using the default
random engine
43/84
Seed 1/3
Given a seed, the generator produces always the same sequence
The seed could be selected randomly by using the current time:
#include <random>
#include <chrono>
unsigned seed = std::chrono::system_clock::now()
.time_since_epoch().count();
std::default_random_engine generator(seed);
chrono::system clock::now() returns an object representing the current point in time
.time since epoch().count() returns the count of ticks that have elapsed since January 1, 1970
(midnight UTC/GMT)
Problem: Consecutive calls return very similar seeds
44/84
Seed 2/3
A random device std::random device is a uniformly distributed integer generator
that produces non-deterministic random numbers (e.g. from a hardware device)
Note: Not all systems provide a random device
#include <random>
std::random_device rnd_device;
std::default_random_engine generator(rnd_device());
std::seed seq consumes a sequence of integer-valued data and produces a number
of unsigned integer values in the range [0, 2
32
− 1]. The produced values are
distributed over the entire 32-bit range even if the consumed values are close
45/84
Seed 3/3
#include <random>
#include <chrono>
unsigned seed1 = std::chrono::system_clock::now()
.time_since_epoch().count();
unsigned seed2 = seed1 + 1000;
std::seed_seq seq1{ seed1, seed2 };
std::default_random_engine generator1(seq);
std::random_device rnd;
std::default_random_engine generator1(rnd());
46/84
PRNG Period and Quality
PRNG Period
The period (or cycle length) of a PRNG is the length of the sequence of numbers that the
PRNG generates before repeating
PRNG Quality
(informal) If it is hard to distinguish a generator output from truly random sequences, we call it
a high quality generator. Otherwise, we call it low quality generator
Generator Quality Period Randomness
Linear Congruential Poor 2
31
≈ 10
9
Statistical tests
Mersenne Twister 32/64-bit High 10
6000
Statistical tests
Subtract-with-carry 24/48-bit Highest 10
171
Mathematically proven
47/84
Random Engines
• Linear congruential (LF)
The simplest generator engine. Modulo-based algorithm:
x
i+1
= (αx
i
+ c)mod m where α, c, m are implementation deﬁned
C++ Generators: std::minstd rand , std::minstd rand0 ,
std::knuth b
• Mersenne Twister (M. Matsumoto and T. Nishimura, 1997)
Fast generation of high-quality pseudorandom number. It relies on Mersenne prime number.
(used as default random generator in linux)
C++ Generators: std::mt19937 , std::mt19937 64
• Subtract-with-carry (LF) (G. Marsaglia and A. Zaman, 1991)
Pseudo-random generation based on Lagged Fibonacci algorithm (used for example by
physicists at CERN)
C++ Generators: std::ranlux24 base , std::ranlux48 base , std::ranlux24 , std::ranlux48
48/84
Statistical Tests
The table shows after how many iterations the generator fails the statistical tests
Generator 256M 512M 1G 2G 4G 8G 16G 32G 64G 128G 256G 512G 1T
ranlux24 base ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗
ranlux48 base ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗
minstd rand ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗
minstd rand0 ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗
knuth b ✓ ✓ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗
mt19937 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✗ ✗
mt19937 64 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✗
ranlux24 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
ranlux48 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
49/84
Space and Performance
Generator Predictability State Performance
Linear Congruential Trivial 4-8 B Fast
Knuth Trivial 1 KB Fast
Mersenne Twister Trivial 2 KB Good
randlux base Trivial 8-16 B Slow
randlux Unknown? ∼120 B Super slow
50/84
Distribution
• Uniform distribution uniform int distribution<T>(range start, range end)
where T is integral type
uniform real distribution<T>(range start, range end) where T is ﬂoating
point type
• Normal distribution P (x) =
1
σ
√
2π
e
−
(x−µ)
2
2σ
2
normal distribution<T>(mean, std dev)
where T is ﬂoating point type
• Exponential distribution P (x , λ) = λe
−λx
exponential distribution<T>(lambda)
where T is ﬂoating point type
51/84
Examples
unsigned seed = ...
// Original linear congruential
minstd_rand0 lc1 generator(seed);
// Linear congruential (better tuning)
minstd_rand lc2_generator(seed);
// Standard mersenne twister (64-bit)
mt19937_64 mt64_generator(seed);
// Subtract-with-carry (48-bit)
ranlux48_base swc48_generator(seed);
uniform_int_distribution<int> int_distribution(0, 10);
uniform_real_distribution<float> real_distribution(-3.0f, 4.0f);
exponential_distribution<float> exp_distribution(3.5f);
normal_distribution<double> norm_distribution(5.0, 2.0);
52/84
References
PRNG Quality:
• On C++ Random Number Generator Quality
• It is high time we let go of the Mersenne Twister
• The Xorshift128+ random number generator fails BigCrush
Recent algorithms:
• PCG, A Family of Better Random Number Generators
• Xoshiro / Xoroshiro generators and the PRNG shootout
53/84
Performance Comparison
Random number generators for C++ performance tested
54/84
Quasi-random 1/2
The quasi-random numbers have the low-discrepancy property that is a measure of
uniformity for the distribution of the point for the multi-dimensional case
• Quasi-random sequence, in comparison to pseudo-random sequence, distributes
evenly, namely this leads to spread the number over the entire region
• The concept of low-discrepancy is associated with the property that the successive
numbers are added in a position as away as possible from the other numbers that
is, avoiding clustering (grouping of numbers close to each other)
55/84
Quasi-random 2/2
Pseudo-random vs. Quasi random
56/84
Time Measuring
Time Measuring 1/2
Wall-Clock/Real time
It is the human perception of the passage of time from the start to the completion of
a task
User/CPU time
The amount of time spent by the CPU to compute in user code
System time
The amount of time spent by the CPU to compute system calls (including I/O calls)
executed into kernel code
57/84
Time Measuring 2/2
The Wall-clock time measured on a concurrent process platform may include the time
elapsed for other tasks
The User/CPU time of a multi-thread program is the sum of the execution time of all
threads
If the system workload (except the current program) is very low and the program uses
only one thread then
Wall-clock time = User time + System time
58/84
Time Measuring - Wall-Clock Time 1/3
::gettimeofday() : time resolution 1µs
# include <time.h> //struct timeval
# include <sys/time.h> //gettimeofday()
struct timeval start, end; // timeval {second, microseconds}
::gettimeofday(&start, NULL);
... // code
::gettimeofday(&end, NULL);
long start_time = start.tv_sec * 1000000 + start.tv_usec;
long end_time = end.tv_sec * 1000000 + end.tv_usec;
cout << "Elapsed: " << end_time - start_time; // in microsec
Problems: Linux only (not portable), the time is not monotonic increasing (timezone), time
resolution is big
59/84
Time Measuring - Wall-Clock Time 2/3
std::chrono C++11
# include <chrono>
auto start_time = std::chrono::system_clock::now();
... // code
auto end_time = std::chrono::system_clock::now();
std::chrono::duration<double> diff = end_time - start_time;
cout << "Elapsed: " << diff.count(); // in seconds
cout << std::chrono::duration_cast<milli>(diff).count(); // in ms
Problems: The time is not monotonic increasing (timezone)
60/84
Time Measuring - Wall-Clock Time 3/3
An alternative of system clock is steady clock which ensures monotonic
increasing time.
steady clock is implemented over clock gettime on POSIX system and has 1ns
time resolution
# include <chrono>
auto start_time = std::chrono::steady_clock::now();
... // code
auto end_time = std::chrono::steady_clock::now();
However, the overhead of C++ API is not always negligible, e.g.
Linux libstdc++ → 20ns, Mac libc++ → 41ns
Measuring clock precision
61/84
Time Measuring - User Time
std::clock , implemented over clock gettime on POSIX system and has 1ns
time resolution
# include <chrono>
clock_t start_time = std::clock();
... // code
clock_t end_time = std::clock();
float diff = static_cast<float>(end_time - start_time) / CLOCKS_PER_SEC;
cout << "Elapsed: " << diff; // in seconds
62/84
Time Measuring - User/System Time
# include <sys/times.h>
struct ::tms start_time, end_time;
::times(&start_time);
... // code
::times(&end_time);
auto user_diff = end_time.tmus_utime - start_time.tms_utime;
auto sys_diff = end_time.tms_stime - start_time.tms_stime;
float user = static_cast<float>(user_diff) / ::sysconf(_SC_CLK_TCK);
float sys = static_cast<float>(sys_diff) / ::sysconf(_SC_CLK_TCK);
cout << "user time: " << user; // in seconds
cout << "system time: " << sys; // in seconds
63/84
Std Classes
std::pair 1/2
<utility>
std::pair class couples together a pair of values, which may be of diﬀerent types
Construct a std::pair
• std::pair<T1, T2> pair(value1, value2)
• std::pair<T1, T2> pair = {value1, value2}
• auto pair = std::make pair(value1, value2)
Data members:
• first access ﬁrst ﬁeld
• second access second ﬁeld
Methods:
• comparison ==, <, >, ≥, ≤
• swap std::swap
64/84
std::pair 2/2
# include <utility>
std::pair<int, std::string> pair1(3, "abc");
std::pair<int, std::string> pair2 = { 4, "zzz" };
auto pair3 = std::make_pair(3, "hgt");
cout << pair1.first; // print 3
cout << pair1.second; // print "abc"
swap(pair1, pair2);
cout << pair2.first; // print "zzz"
cout << pair2.second; // print 4
cout << (pair1 > pair2); // print 1
65/84
std::tuple 1/3
<tuple>
std::tuple is a ﬁxed-size collection of heterogeneous values. It is a generalization of
std::pair . It allows any number of values
Construct a std::tuple (of size 3)
• std::tuple<T1, T2, T3> tuple(value1, value2, value3)
• std::tuple<T1, T2, T3> tuple = {value1, value2, value3}
• auto tuple = std::make tuple(value1, value2, value3)
Data members:
std:get<I>(tuple) returns the i-th value of the tuple
Methods:
• comparison ==, <, >, ≥, ≤
• swap std::swap
66/84
std::tuple 2/3
• auto t3 = std::tuple cat(t1, t2)
concatenate two tuples
• const int size = std::tuple size<TupleT>::value
returns the number of elements in a tuple at compile-time
• using T = typename std::tuple element<TupleT>::type obtains the
type of the speciﬁed element
• std::tie(value1, value2, value3) = tuple
creates a tuple of references to its arguments
• std::ignore
an object of unspeciﬁed type such that any value can be assigned to it with no
eﬀect
67/84
std::tuple 3/3
# include <tuple>
std::tuple<int, float, char> f() { return {7, 0.1f, 'a'}; }
std::tuple<int, char, float> tuple1(3, 'c', 2.2f);
auto tuple2 = std::make_tuple(2, 'd', 1.5f);
cout << std::get<0>(tuple1); // print 3
cout << std::get<1>(tuple1); // print 'c'
cout << std::get<2>(tuple1); // print 2.2f
cout << (tuple1 > tuple2); // print true
auto concat = std::tuple_cat(tuple1, tuple2);
cout << std::tuple_size<decltype(concat)>::value; // print 6
using T = std::tuple_element<4, decltype(concat)>::type; // T is int
int value1; float value2;
std::tie(value1, value2, std::ignore) = f();
68/84
std::variant 1/3
<variant> C++17
std::variant represents a type-safe union as the corresponding objects know
which type is currently being held
It can be indexed by:
• std::get<index>(variant) an integer
• std::get<type>(variant) a type
# include <variant>
std::variant<int, float, bool> v(3.3f);
int x = std::get<0>(v); // return integer value
bool y = std::get<bool>(v); // return bool value
// std::get<0>(v) = 2.0f; // run-time exception!!
69/84
std::variant 2/3
Another useful method is index() which returns the position of the type currently
held by the variant
# include <variant>
std::variant<int, float, bool> v(3.3f);
cout << v.index(); // return 1
std::get<bool>(v) = true
cout << v.index(); // return 2
70/84
std::variant + Visitor 3/3
It is also possible to query the index at run-time depending on the type currently being
held by providing a visitor
# include <variant>
struct Visitor {
void operator()(int& value) { value *= 2; }
void operator()(float& value) { value += 3.0f; } // <--
void operator()(bool& value) { value = true; }
};
std::variant<int, float, bool> v(3.3f);
std::visit(v, Visitor{});
cout << std::get<float>(v); // 6.3f
71/84
std::optional 1/2
<optional> C++17
std::optional provides facilities to represent potential “no value” states
As an example, it can be used for representing the state when an element is not found
in a set
# include <optional>
std::optional<std::string> find(const char* set, char value) {
for (int i = 0; i < 10; i++) {
if (set[i] == value)
return i;
}
return {}; // std::nullopt;
}
72/84
std::optional 2/2
# include <optional>
char set[] = "sdfslgfsdg";
auto x = find(set, 'a'); // 'a' is not present
if (!x)
cout << "not found";
if (!x.has_value())
cout << "not found";
auto y = find(set, 'l');
cout << *y << " " << y.value(); // print '4' '4'
x.value_or(-1); // returns '-1'
y.value_or(-1); // returns '4'
73/84
std::any
<any> C++17
std::any holds arbitrary values and provides type-safety
# include <any>
std::any var = 1; // int
cout << var.type().name(); // print 'i'
cout << std::any_cast<int>(var);
// cout << std::any_cast<float>(var); // exception!!
var = 3.14; // double
cout << std::any_cast<double>(var);
var.reset();
cout << var.has_value(); // print 'false'
74/84
std::stacktrace 1/2
C++23 introduces std::stacktrace library to get the current function call stack,
namely the sequence of calls from the main() entry point
# include <print>
# include <stacktrace> // the program must be linked with the library
// -lstdc++_libbacktrace
// (-lstdc++exp with gcc-14 trunk)
void g() {
auto call_stack = std::stacktrace::current();
for (const auto& entry : call_stack)
std::print("{}\n", entry);
}
void f() { g(); }
int main() { f(); }
75/84
std::stacktrace 2/2
the previous code prints
g() at /app/example.cpp:6
f() at /app/example.cpp:11
main at /app/example.cpp:13
at :0
__libc_start_main at :0
_start at :0
The library also provides additional functions for entry to allow ﬁne-grained control
of the output description() , source file() , source line()
for (const auto& entry : call_stack) { // same output
std::print("{} at {}:{}\n", entry.description(), entry.source_file(),
entry.source_line());
}
76/84
Filesystem Library
Filesystem Library
C++17 introduces abstractions and facilities for performing operations on ﬁle systems
and their components, such as paths, ﬁles, and directories
• Follow the Boost ﬁlesystem library
• Based on POSIX
• Fully-supported from clang 7, gcc 8, etc.
• Work on Windows, Linux, Android, etc.
77/84
Basic concepts
• ﬁle: a ﬁle system object that holds data
◦ directory a container of directory entries
◦ hard link associates a name with an existing ﬁle
◦ symbolic link associates a name with a path
◦ regular ﬁle a ﬁle that is not one of the other ﬁle types
• ﬁle name: a string of characters that names a ﬁle. Names . (dot) and ..
(dot-dot) have special meaning at library level
• path: sequence of elements that identiﬁes a ﬁle
◦ absolute path: a path that unambiguously identiﬁes the location of a ﬁle
◦ canonical path: an absolute path that includes no symlinks, . or .. elements
◦ relative path: a path that identiﬁes a ﬁle relative to some location on the ﬁle system
78/84
path Object
A path object stores the pathname in native form
# include <filesystem> // required
namespace fs = std::filesystem;
fs::path p1 = "/usr/lib/sendmail.cf"; // portable format
fs::path p2 = "C:\\users\\abcdef\\"; // native format
cout << "p1: " << p1; // /usr/lib/sendmail.cf
cout << "p2: " << p2; // C:\users\abcdef\
out << "p3: " << p2 + "xyz\\"; // C:\users\abcdef\xyz\
79/84
path Methods
Decomposition (member) methods:
• Return root-name of the path
root name()
• Return path relative to the root path
relative path()
• Return the path of the parent path
parent path()
• Return the ﬁlename path component
filename()
• Return the ﬁle extension path component
extension()
80/84
Filesystem Methods - Query
• Check if a ﬁle or path exists
exists(path)
• Return the ﬁle size
file size(path)
• Check if a ﬁle is a directory
is directory(path)
• Check if a ﬁle (or directory) is empty
is empty(path)
• Check if a ﬁle is a regular ﬁle
is regular file(path)
• Returns the current path
current path()
81/84
Directory Iterators
Iterate over ﬁles of a directory (recursively/non-recursively)
# include <filesystem>
namespace fs = std::filesystem;
for(auto& path : fs::directory_iterator("/usr/tmp/"))
cout << path << '\n';
for(auto& path : fs::recursive_directory_iterator("/usr/tmp/"))
cout << path << '\n';
82/84
Filesystem Methods - Modify
• Copy ﬁles or directories
copy(path1, path2)
• Copy ﬁles
copy file(src path, src path, [fs::copy options::recursive])
• Create new directory
create directory(path)
• Remove a ﬁle or empty directory
remove(path)
• Remove a ﬁle or directory and all its contents, recursively
remove all(path)
• Rename a ﬁle or directory
rename(old path, new path)
83/84
Examples
# include <filesystem> // required
namespace fs = std::filesystem;
fs::path p1 = "/usr/tmp/my_file.txt";
cout << p1.exists(); // true
cout << p1.parent_path(); // "/usr/tmp/"
cout << p1.filename(); // "my_file"
cout << p1.extension(); // "txt"
cout << p1.is_directory(); // false
cout << p1.is_regular_file(); // true
fs::create_directory("/my_dir/");
fs::copy(p1.parent_path(), "/my_dir/", fs::copy_options::recursive);
fs::copy_file(p1, "/my_dir/my_file2.txt");
fs::remove(p1);
fs::remove_all(p1.parent_path());
84/84