Cool cpp features
Outline some of the (not so) cool cpp features over the years and the versions and the specifications.
Cool cpp features
I got annoyed and began reading through the cpp features github page. It's not bad per se. Here's a dump:
C++ 11
I'm not mentioning stuff that got deprecated later on, or stuff that's trivial.
std::move/Move semantics/Rvalue reference
See move semantics
std::forward and perfect forwarding
See 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.
C++ Memory model
See the note on 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.2decltype
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 ++ ...)
(++ ... 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 referenceinitialization 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 typeVariant 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 transform_reduce
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 maybe_unused
[[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; // == falselikely and unlikely attributes
[[likely]] and [[unlikely]] are
compiler hints that may optimize a particular branch.
char8_t
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.