Some Modern C++ Techniques

Some Modern C++ Techniques

Some code and techniques in modern C++(11, 14, 17, 20(just 2a)).

Structured Binding

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <vector>

struct Node {
int v, w;
};

int main() {
std::vector<Node> vec{{1, 2}, {3, 4}};
for (auto &[v, w] : vec) {
w += v;
std::cout << v << ' ' << w << '\n';
}
}

Fold Expressions

unary left fold

... op args $\rightarrow$ ((e1 op e2) op ...) op en

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename... Args>
auto sum(Args &&... args) {
return (... + args);
}

template <typename... Args>
auto sub(Args &&... args) {
return (... - args);
}

int main() {
std::cout << sum(1, 2, 3, 4) << std::endl; // (((1 + 2) + 3) + 4)
std::cout << sub(1, 2, 3, 4) << std::endl; // (((1 - 2) - 3) - 4)
return 0;
}

unary right fold

args op ... $\rightarrow$ e1 op (... op (en-1 op en))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename... Args>
auto sum(Args &&... args) {
return (args + ...);
}

template <typename... Args>
auto sub(Args &&... args) {
return (args - ...);
}

int main() {
std::cout << sum(1, 2, 3, 4) << std::endl; // (1 + (2 + (3 + 4)))
std::cout << sub(1, 2, 3, 4) << std::endl; // (1 - (2 - (3 - 4)))
return 0;
}

binary left fold

I op ... op args $\rightarrow$ ((I op e1) op ...) op en

1
2
3
4
5
6
7
8
9
template <typename... Args>
auto sub(Args &&... args) {
return (1 - ... - args);
}

int main() {
std::cout << sub(2, 3, 4) << std::endl; // (((1 - 2) - 3) - 4)
return 0;
}

binary right fold

args op ... op I $\rightarrow$ e1 op (... op (en op I))

1
2
3
4
5
6
7
8
9
template <typename... Args>
auto sub(Args &&... args) {
return (args - ... - 1);
}

int main() {
std::cout << sub(2, 3, 4) << std::endl; // (2 - (3 - (4 - 1)))
return 0;
}

Numeric Init

1
int a = 1'000'000;

User-Defined Literals

1
2
3
4
5
6
7
8
9
10
#include <iostream>

constexpr long double operator"" _deg(long double deg) {
return deg * 3.14159265358979323846264L / 180;
}

int main() {
double x = 90.0_deg;
std::cout << x << '\n';
}

Template Argument Deduction

1
2
3
4
std::pair p = {1, 5.2};  // p is a std::pair<int, double>

std::vector v1{1, 2, 3, 4, 5};
std::vector v2(v1.begin(), v1.end());

Variable Templates

1
2
template <typename T>
constexpr T x = 1;

Aliases

1
2
3
4
template <typename T>
using StringMap = std::map<std::string, T>;

StringMap<int> map; // map is a std::map<std::string, int>

Compile-Time if

Consider writing an operation that can use one of two operations slowAndSafe(T) or simpleAndFast(T).

1
2
3
4
5
6
7
template<typename T>
void update(T &target) {
if constexpr(std::is_pod<T>::value)
simpleAndFast(target); // for "plain old data"
else
slowAndSafe(target);
}

Variadic Templates

1
2
3
4
5
6
template <typename T, typename... Tail>
void print(T head, Tail... tail) {
// what we do for each argument, e.g.,
std::cout << head << ' ';
if constexpr (sizeof...(tail) > 0) print(tail...);
}

Essential Operations

1
2
3
4
5
6
7
8
9
10
11
class X {
public:
X(Sometype); // "ordinary constructor": create an object
X(); // default constructor
X(const X &); // copy constructor
X(X &&); // move constructor
X &operator=(const X &); // copy assignment: clean up target and copy
X &operator=(X &&); // move assignment: clean up target and move
~X(); // destructor: clean up
// ...
};

use = default to generate default constructor, use = delete to delete operations.

Conversions

use explicit to eliminate implicit conversions.

1
2
3
4
class Vector {
public:
explicit Vector(int); // no implicit conversion from int to Vector
};

Member Initializers

When a data member of a class is defined, we can supply a default initializer called a default member initializer.

1
2
3
4
struct Point {
double x = 0;
double y = 0;
};

String

Literals

1
2
3
4
5
6
7
8
#include <string>

using namespace std::literals::string_literals;

int main() {
auto s = "123"s; // std::string
return 0;
}

String Views

a string_view is basically a (pointer, length) pair denoting a sequence of characters:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <string>
#include <string_view>
#include <algorithm>

using namespace std::literals::string_literals;
using namespace std::literals::string_view_literals;

std::string cat(std::string_view sv1, std::string_view sv2) {
std::string res(sv1.length() + sv2.length(), '\0');
char *p = res.data();
for (char c : sv1) // one way to copy
*p++ = c;
std::copy(sv2.begin(), sv2.end(), p); // another way
return res;
}

int main() {
std::string king = "Harold";
auto s1 = cat(king, "William"); // string and const char *
auto s2 = cat(king, king); // string and string
auto s3 = cat("Edward", "Stephen"sv); // const char *and string_view
auto s4 = cat("Canute"sv, king);
auto s5 = cat({&king[0], 2}, "Henry"sv); // HaHenry
auto s6 = cat({&king[0], 2}, {&king[2], 4}); // Harold
return 0;
}

Utilities

unique_ptr and shared_ptr

1
2
std::unique_ptr<int> a = std::make_unique<int>(1);
auto p = std::make_shared<int>(1);

move

1
2
3
4
5
6
template <typename T>
void swap(T &a, T &b) {
T tmp{std::move(a)};
a = std::move(b);
b = std::move(tmp);
}

Perfect forwarding

1
2
3
4
template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args &&... args) {
return std::unique_ptr<T>{new T{std::forward<Args>(args)...}}; // forward each argument
}

variant

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <variant>

int main() {
std::variant<int, float> v;
v = 1.0f;
if (std::holds_alternative<int>(v)) {
std::cout << std::get<int>(v);
} else {
std::cout << std::fixed << std::get<float>(v);
}
return 0;
}

optional

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <optional>

std::optional<int> get(int x) {
if (x < 0) return std::nullopt;
return x;
}

int main() {
auto x = get(-1);
std::cout << x.value_or(0);
x = get(1);
if (x) std::cout << *x;
return 0;
}

any

The class any describes a type-safe container for single values of any type.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <any>
#include <iostream>

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

// any type
std::any a = 1;
std::cout << a.type().name() << ": " << std::any_cast<int>(a) << '\n';
a = 3.14;
std::cout << a.type().name() << ": " << std::any_cast<double>(a) << '\n';
a = true;
std::cout << a.type().name() << ": " << std::any_cast<bool>(a) << '\n';

// bad cast
try {
a = 1;
std::cout << std::any_cast<float>(a) << '\n';
} catch (const std::bad_any_cast &e) {
std::cout << e.what() << '\n';
}

// has value
a = 1;
if (a.has_value()) {
std::cout << a.type().name() << '\n';
}

// reset
a.reset();
if (!a.has_value()) {
std::cout << "no value\n";
}

// pointer to contained data
a = 1;
int *i = std::any_cast<int>(&a);
std::cout << *i << "\n";
}

Time

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <chrono>
#include <cmath>
#include <thread>

int main() {
using namespace std::chrono_literals;
auto func = []() {
double ret = 0;
for (int i = 0; i < 10000000; i++) ret += sin(i);
return ret;
};
auto start = std::chrono::high_resolution_clock::now();
std::cout << func() << std::endl;
auto end = std::chrono::high_resolution_clock::now();
std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
<< "ms\n";
start = std::chrono::high_resolution_clock::now();
std::this_thread::sleep_for(10ms + 33us);
end = std::chrono::high_resolution_clock::now();
std::cout << std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()
<< "us\n";
}

mem_fn

Function template std::mem_fn generates wrapper objects for pointers to members, which can store, copy, and invoke a pointer to member. Both references and pointers (including smart pointers) to an object can be used when invoking a std::mem_fn.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <functional>
#include <iostream>

struct Foo {
void greet() { std::cout << "Hello, world.\n"; }
void number(int i) { std::cout << "number: " << i << '\n'; }
int data = 7;
};

int main() {
Foo f;
auto greet = std::mem_fn(&Foo::greet);
greet(f);
auto print = std::mem_fn(&Foo::number);
print(f, 42);
auto data = std::mem_fn(&Foo::data);
std::cout << "data: " << data(f) << '\n';
}

enable_if

1
2
3
4
5
6
7
8
9
#include <functional>
#include <iostream>

template <typename T>
struct SmartPointer {
T &operator*();
std::enable_if<std::is_class_v<T>(), T &> operator->();
// is defined if and only if T is a class
};

gcd and lcm

1
2
3
4
#include <iostream>
#include <numeric>

int main() { std::cout << std::gcd(6, 9) << ' ' << std::lcm(6, 9); }

mutex

shared_mutex

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <shared_mutex>

std::shared_mutex mutex; // a mutex that can be shared

void reader() {
std::shared_lock lock{mutex}; // willing to share access with other readers
// ... read ...
}
void writer() {
std::unique_lock lck{mutex}; // needs exclusive(unique) access
// ... write ...
}

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×