InnocentZero's Treasure Chest

HomeFeedAbout Me

13 Dec 2024

Cool C++ features

C++ 11

I'm not mentioning stuff that got deprecated later on, or stuff that's trivial.

std::move/Move semantics/Rvalue reference

std::forward

Type traits/Static Asserts

Type traits are a compile time checking/querying of template types. This is a library feature. Static asserts are a language feature allowing you to do compile time checks of things that are compile time evaluatable.

Memory model

Variadic templates

Allow for templates with a variable number of template arguments. ... creates a parameter pack or expands it. Can be useful for iterating over template arguments:

template <typename First, typename... Args>
auto sum(const First first, const Args... args) -> decltype(first) {
  const auto values = {first, args...};
  return std::accumulate(values.begin(), values.end(), First{0});
}

sum(1, 2, 3, 4, 5); // 15
sum(1, 2, 3);       // 6
sum(1.5, 2.0, 3.7); // 7.2

decltype

Used for inferring the type of an expression not known apriori.

Final

If marked on virtual functions, they cannot be overridden. If marked on classes/structs, they cannot be inherited from.

Reference Qualified members

Allows you to choose the overload based on the type of *this. Supports const, lvalues, and rvalues (probably others as well).

noexcept

This is used to annotate functions that don't throw an exception. If an exception is thrown in their call stack, std::terminate is called instead.

C++ 14

Binary literals

0b1100 are valid binary numbers.

decltype(auto)

While auto deduces the type, this form preserves the reference and cv-qualifier status.

C++ 17

Folding expressions

This is done over a parameter pack. For unary operators it is (e ++ ...) or (++ ... e) Similarly for binary operators it is (cout << ... << e).

Structured bindings

Pattern matching basically. Use as follows:

int a[2] = {1, 2}; // also works for std::array, std::tuple, std::initializer_list, std::pair

auto [x, y] = a; // size must match

auto &[xr, yr] = a; // by reference

initialization in branches

{
std::lock_guard<std::mutex> lk(mx);
if (v.empty()) v.push_back(val);
}
// vs.
if (std::lock_guard<std::mutex> lk(mx); v.empty()) {
v.push_back(val);
}

Class Template Argument Deduction

This helps the compiler in deducing the template argument types using the arguments given to the constructor of the template class.

template <typename T>
struct container {
  container(T t) {}

  template <typename Iter>
  container(Iter beg, Iter end);
};

// deduction guide
template <typename Iter>
container(Iter b, Iter e) -> container<typename std::iterator_traits<Iter>::value_type>;

container a{ 7 }; // OK: deduces container<int>

std::vector<double> v{ 1.0, 2.0, 3.0 };
auto b = container{ v.begin(), v.end() }; // OK: deduces container<double>

container c{ 5, 6 }; // ERROR: std::iterator_traits<int>::value_type is not a type

TODO Variant and optional

Variants can hold multiple types. Optional is like option monad. They have specialized operators and functions.

String view

Kind of like &str in Rust, provides a non-owning reference to the string, for modification to be done on it. Most useful for trimming.

std::filesystem

Provides a unified method of interacting with the underlying host OS filesystem.

std::byte

This is supposed to be the standard way of representing a byte. This only has overloads for bitwise operations, and is therefore more in line with how a byte should behave. Can be typecasted to one of the arithmetic types.

RB-Tree splicing

This allows you to cheaply remove nodes from one map and insert it to another. The method name is set::extract and it takes an iterator or a key as an argument. This also returns the object node so it can outlive the container. Also allows one to cheaply remove the node, change its key, and reinsert it.

std::map<int, string> m {{1, "one"}, {2, "two"}, {3, "three"}};
auto e = m.extract(2);
e.key() = 4;
m.insert(std::move(e));
// m == { { 1, "one" }, { 3, "three" }, { 4, "two" } }

For merging one set into another, set::merge is also provided. It works like this.

std::set<int> src {1, 3, 5};
std::set<int> dst {2, 4, 5};
dst.merge(src);
// src == { 5 } // duplicates are preserved
// dst == { 1, 2, 3, 4, 5 }

Fold operations: reduce and transformreduce

These allow you to operate over containers the way functional programming does. This is done in parallel so operators need to be commutative and associative. Else just use std::accumulate.

const std::array<int, 3> a{ 1, 2, 3 };
std::reduce(std::cbegin(a), std::cend(a), 1, std::multiplies<>{}); // == 6
std::transform_reduce(std::cbegin(a), std::cend(a), 0, std::plus<>{}, times_ten);

nodiscard and maybeunused

  • [[nodiscard]] is used to imply that the return value must not be ignored. It can also be applied on types.
  • [[maybe_unused]] means that the parameter maybe unused so there should not be a warning for that.

C++ 20

Concepts

This is like trait bounds in Rust and typeclasses in haskell, as it encapsulates the behaviour and requirements that template arguments should possess.

// Forms for function parameters:
// `T` is a constrained type template parameter.
template <my_concept T>
void f(T v);

// `T` is a constrained type template parameter.
template <typename T>
  requires my_concept<T>
void f(T v);

// `T` is a constrained type template parameter.
template <typename T>
void f(T v) requires my_concept<T>;

// `v` is a constrained deduced parameter.
void f(my_concept auto v);

// `v` is a constrained non-type template parameter.
template <my_concept auto v>
void g();

// Forms for auto-deduced variables:
// `foo` is a constrained auto-deduced value.
my_concept auto foo = ...;

Three way comparison

We have a three way comparison operator that returns a type of ordering that is used to decide all of other relational operators at once.

The three orderings are:

  • std::strong_ordering: This differentiates between objects being identical and equal.
  • std::weak_ordering: The regular one.
  • std::partial_ordering: This allows for objects to not be orderable as well.
struct foo {
  int a;
  bool b;
  char c;

  // Compare `a` first, then `b`, then `c` ...
  auto operator<=>(const foo&) const = default;
};

foo f1{0, false, 'a'}, f2{0, true, 'b'};
f1 < f2; // == true
f1 == f2; // == false
f1 >= f2; // == false

likely and unlikely attributes

[[likely]] and [[unlikely]] are compiler hints that may optimize a particular branch.

char8t

Standard type for a character. Similarly, char16_t and char32_t exist for larger characters.

std::format

Allows one to format a string the same way sprintf and asprintf do. Format specifiers are also used.

std::span

A non-owning view of a container. This can be dynamically sized or statically sized. Static ones benefit from bounds-checking. Use as std::span<T, size> or std::span<T>.

<bit> header

This header provides convenience functions like popcount. We also have bit_cast for safe reinterpret of one type to another.

prefix and suffix checks on strings

These are used as s.starts_with and s.ends_with.

std::midpoint

std::midpoint provides a safe way to find the mid point of two integers without overflow.

bind_front

Partial function application :) Binds the first \(n\) arguments.

Tags: programming C++

Other posts
Creative Commons License
This website by Md Isfarul Haque is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.