Lost_Arrow's blog

By Lost_Arrow, 3 weeks ago, In English

Some of you may remember me from a recent blog of mine regarding the request for the addition of the GCC 10.2.0 compiler for the partial support of C++20. I mentioned in the blog that if enough people wanted to know more then I'd write a simple tutorial on it, and so here it is! Thank you to everyone who reached out on PMs and my friends on Discord.

Keep in mind that I'm not very experienced with all aspects of C++20 or even pre-C++20 to be able to show the usage of every possible feature and probable effects on CP. I'm learning and discovering new things everyday and you can consider this as my personal overview and summary of it; which beginners like myself can make good use of to explore new concepts in programming.

Feel free to add comments about anything I've missed (which is undoubtedly a lot) and I'll update the blog to summarise as much as I can (ofcourse, with your permission). Also, feel free to correct me if I'm wrong at any point in the blog and suggest any relevant changes/updates.

Thank you for coming over to my blog and spending your precious time reading it!


C++20 - Features and Introduction


Overview

New Features

Constraints and Concepts
Spaceship <=> operator
Pack expansion in lambda init-captures and Templated lambdas
Modules
consteval, constinit and improvements to constexpr
Designated initializers
Initializer statements in range based for loops
Coroutines
Feature test macros

New Headers

concepts
ranges
bit
numbers
coroutine
syncstream
compare
format
span
source_location
version
stop_token
latch
barrier
semaphore

Uses and Examples

>> Concepts

This is my favourite feature of C++20. There is no need for complicated pre-C++20 SFINAE techniques anymore. You can freely write generic code with the assurity that the required type satisfies certain properties you need it to.
Concepts are a named set of requirements and are evaluated at compile-time only and have no effects on your code runtime. These are used to constrain template parameters by making them part of the template interface.

The general form used to declare a concept is:

template <template_parameters>
concept concept_name = constraint_expression;

The constraint expression uses the keyword requires with its required parameter list you'd like to do compile-time checking on followed by the constraint body where you write your checks. The constraint_expression results in a constexpr bool value, which if is false (i.e. failure of the required constraint) will result in compilation failure with a neat compilation error message (and not the millions of template errors everyone knows about).

Let me give you a simple example of how to write and use one:

// Our concept "incrementable" requires that we can perform the operation
// ++x on it. If we can't, constraint expression results in false and leads
// to compilation error.
template <typename T>
concept incrementable = requires (T x) {
	++x;
};

// A simple function that increments a value by 1 using prefix increment
template <typename T> requires incrementable <T>
void increment (T& x) {
	++x;
}

auto main () -> int32_t {

	int n = 10;
	std::cout << n << '\n'; // prints 10
	increment(n); // okay, constraint is satisfied
	std::cout << n << '\n'; // prints 11

	std::vector <int> v {1, 2, 3, 4, 5};
	// increment(v); // error, constraints not satisfied: the required expression (++x) is invalid
	increment(v.front()); // okay
	std::cout << v.front(); // prints 2

	return 0;
}

There are four ways of using a concept to constrain template parameters that I know of, and they are:

// The concept
template <typename T>
concept summable = requires (T x, T y) {
	{x + y};
	{x += y};
};

// first way - using constraint clause after template parameter list
template <typename T> requires summable <T>
T sum (T x, T y) {
	return x + y;
}

// second way - using constraint clause after function declaration
template <typename T>
T sum (T x, T y) requires summable <T> {
	return x + y;
}

// third way - using constraint clause as template parameter property
template <summable T>
T sum (T x, T y) {
	return x + y;
}

// fourth way - using magic. just define the concept as part of the function.
// Note that there are two ways you could write this: as a "first way" or
// as a "second way". The example below uses the "second way".
template <typename T>
T add (T x, T y) requires
requires (T x) {
	x + x;
} {
	return x + y;
}

// concepts can be used directly in function parameter list too (and similarly in
// lambdas too)
void foo (summable auto T) {
    // ...
}

// concepts can also be used with templated lambdas
std::cout << [] <typename T> (T x, T y) -> T requires
requires (T x) {
	x + x;
} {
	return x + y;
} (10, 20); // prints 30

Other than the above examples for introductory purpose, here are a few more examples of writing code with and without the use of concepts giving you some insight on how things change in C++20 compared to pre-C++20. (The Debug related template using SFINAE techniques is somewhat a replica of what Errichto uses)

C++20 I/O Template with Concepts
Pre-C++20 I/O Template without concepts using SFINAE techniques
C++20 Debug Template with concepts
Pre-C++20 Debug Template without concepts using SFINAE techniques

Concepts can also be combined using the && and || operators.

template <typename T>
concept has_begin = requires (T x) {
	x.begin();
};

template <typename T>
concept has_end = requires (T x) {
	x.end();
};

template <typename T>
requires has_begin <T> && has_end <T>
void container_reader (T& t) {
	// ...
}

Concepts cannot be used recursively or be self-constrained with other constraints. However, you can add constraints to the constraint expressions of other concepts.

In my personal and honest opinion, I don't really think that concepts are going to be of much use in CP. They sure are powerful but you're not gonna need them most likely. However, if you are the kind of person who likes maintaining templates for data structures, this might be a very useful feature for you. If you see or know otherwise, please share in the comments.

>> Spaceship <=> operator

The <=>, also famously known as the "spaceship" or "three-someway comparison", operator can be used just like the two-way comparison operators, but there are a few differences.

  • Two-way comparisons yield a bool prvalue result.

  • Three-way comparisons yield a prvalue of type std::strong_ordering for integral types and std::partial_ordering for floating point types.

From cppreference,

std::strong_ordering::less if a < b
std::strong_ordering::equal if a == b
std::strong_ordering::greater if a > b

std::partial_ordering::less if a is lesser than b
std::partial_ordering::greater if a is greater than b
std::partial_ordering::equivalent if a is equivalent to b
std::partial_ordering::unordered NaN <=> anything is unordered

Some examples on the usage of the operator:

int a = 10, b = 20, c = 20;
double x = +0.0, y = -0.0, pi = 3.14159, e = 2.718;

std::cout << std::boolalpha;
std::cout << (a <=> b == std::strong_ordering::less) << '\n'     // true
	  << (b <=> c == std::strong_ordering::greater) << '\n'      // false
	  << (a <=> b == std::partial_ordering::less) << '\n'        // true
	  << (c <=> a < 0) << '\n'                                   // false
	  << (a <=> c > 0) << '\n'                                   // false
	  << (a <=> c < 0) << '\n'                                   // true
	  << (a <=> b < 0) << '\n'                                   // true
	  << (b <=> c <= 0) << '\n'                                  // true
	  << (x <=> y == std::partial_ordering::equivalent) << '\n'  // true
	  << (x <=> y == 0) << '\n'                                  // true
	  << (pi <=> e > 0) << '\n'                                  // true
	  << (e <=> pi > 0) << '\n'                                  // false
	  << (a <=> e != 0) << '\n';                                 // true

If you've been programming in C++ for a while, chances are that you've worked with one of your own classes and had to implement the ==, <, >, <=, >= and != operators by hand for relational support. With C++20, you don't need to because the compiler can do it for you. Welcome to the future!

class fraction {
  private:
	int nr, dr;
	void norm () {
		int gcd = std::gcd(nr, dr);
		nr /= gcd;
		dr /= gcd;
	}
  public:
	explicit constexpr fraction (int nr, int dr) : nr (nr), dr (dr) { norm(); }
	auto operator <=> (const fraction& other) const = default;
};

auto main () -> int32_t {

	fraction f (1, 2);
	fraction g (2, 4);
	fraction h (3, 4);

	std::cout << std::boolalpha;
	std::cout << (f == g) << '\n'                       // true
	          << (f  < g) << '\n'                       // false
	          << (h  > g) << '\n'                       // true
	          << (f <=> g > 0) << '\n'                  // false
	          << (h != f) << '\n'                       // true
	          << (g <=> fraction(10, 20) == 0) << '\n'; // true

	return 0;
}

However, when you omit the = default; part and add your own definition, you're going to be thrown some errors.
The reason for this error is that if you write your own definition of <=>, it does not generate the == and != overloads. <=> is an ordering operator and does not provide you with (in)equality.

Just use it this way instead:

class fraction {
  private:
	int nr, dr;
  public:
	explicit constexpr fraction (int nr, int dr) : nr (nr), dr (dr) { }
	auto operator <=> (const fraction& other) const {
		return nr * other.dr <=> dr * other.nr;
	}
};

auto main () -> int32_t {

	fraction f (1, 2);
	fraction g (2, 4);
	fraction h (3, 4);

	std::cout << std::boolalpha;
	std::cout << (f <=> g == 0) << '\n'                 // true
	          << (f <=> g  > 0) << '\n'                 // false
	          << (h <=> g  < 0) << '\n'                 // false
	          << (f <=> g  < 0) << '\n'                 // false
	          << (h <=> f  > 0) << '\n'                 // true
	          << (g <=> fraction(10, 20) == 0) << '\n'; // true

	return 0;
}

Another cool thing about using rocket science in your code is that you can do (4)(see below). Normally, you'd have to define your operator overload as a friend so that when you do something like 42 == a, where a is the my_int user-defined type that takes an int in its constructor, you don't face any errors regarding a non-class type calling operator== because 42 can be constructed to my_int type implicitly. Now, even if you have the overload as a non-friend, you're not going to be thrown any errors because the <=> operator can perform two "actions". If the statement a <=> b fails, then the statement b <=> a is tried too! So, because 42 <=> a fails, it is instead converted to a <=> 42 implicitly and everything happens to be held together with duct tape.

class my_int {
  private:
	int m;
  public:
	constexpr my_int (int m) : m (m) { }
    // this would normally be defined as a friend with let's say operator== or operator< or others...
	// now, you don't have to because of the language understanding symmetricity
	auto operator <=> (const my_int& other) const = default;
};

auto main () -> int32_t {

	my_int a (42);
	my_int b (1729);

	std::cout << (a <=> b >= 0) << '\n'   // 1. false
	          << (a <=> 42 > 0) << '\n'   // 2. false
	          << (a <=> 42 >= 0) << '\n'  // 3. true
	          << (42 <=> a == 0) << '\n'; // 4. true!!!
	
	// The expression 42 <=> a is evaluated as
	// 0 <=> a.operator <=> my_int(42)
	// note that no new definitions of operator overloads are generated by the compiler

	return 0;
}

Actually, this is not true just for <=>. Let me explain using a table I once saw a few months earlier (look below).
(I don't remember the link to the blog I was reading this on but if anyone knows about something that looks similar to this, please link in it in the comments. It was a very helpful blog series iirc)

The primary operators have the ability of being reversed (the way I explain for <=> above). The C++ Language finally understands symmetricity.
The secondary operators have the ability of being redefined (in the normal way in addition to overload of <=> without compilation errors.

+++++++++++++++++++++++++++++++++++++++
+           +           +             +
+           + equality  +  ordering   +
+           +           +             +
+++++++++++++++++++++++++++++++++++++++
+           +           +             +
+ primary   +    ==     +     <=>     +
+           +           +             +
+++++++++++++++++++++++++++++++++++++++
+           +           +             +
+ secondary +    !=     +  < > <= >=  +
+           +           +             +
+++++++++++++++++++++++++++++++++++++++

Now, what does "ability of secondary operators !=, <, >, <=, >= being redefined" mean? Read the comments in the source code below:

// with only <=> overloaded as default
class my_int {
  private:
	int m;
  public:
	constexpr my_int (int m) : m (m) { }
	auto operator <=> (const my_int& other) const = default;
};

auto main () -> int32_t {
	my_int a (42);
	std::cout << (a != 42); // false
	// the above expression (a != 42) works because we've declared operator <=> as default (the
	// case where it provides equality with ordering operators too)
	// the compiler interprets the statement as a.operator <=> my_int(42) != 0 or
	// !(a.operator <=> my_int(42) == 0) (not sure exactly which one but it surely uses <=>)
}

// Redefinition of != in terms of == (read comments)
class my_int {
  private:
	int m;
  public:
	constexpr my_int (int m) : m (m) { }
	bool operator == (const my_int& other) const {
		return m == other.m;
	}
};

auto main () -> int32_t {
	my_int a (42);
	std::cout << (a != 42); // false
	// In C++17, this code will not compile and you will get an error saying no match for operator != found.
	// In C++20, this code is perfectly valid because the language understands symmetricity and rewrite-ability
	// a != b is mathematically equivalent to !(a == b).
	// The compiler implicitly converts a != b to !(a.operator==my_int(b))
}

// So, this is what redefinition means.
// However, if you had an overload for != instead of == in the code right above this comment, then:
a != 42; // okay, there exists an overload !=
42 != a; // error in both C++17 and C++20 as != is not a reversible operator.
// if you were using <=> defaulted overloads instead, it will work in C++20.

As a tip to everyone writing overloads for their operators (I've read this at many places):
Only define the primary operators (== and <=>) for your type.

From a Stack Overflow post, I had copied this source code (but I don't have a link to it because I'm stupid, and now, I can't find the post either):
(Do take a look to see some differences)

// In C++17, you need to write 18 operator overloads for completely implementing ordering and equality
class String {
  string s;

public:
  friend bool operator==(const String& a, const String& b) {
    return a.s.size() == b.s.size() &&
      compare(a.s.c_str(), b.s.c_str()) == 0;
  }
  friend bool operator< (const String& a, const String& b) {
    return compare(a.s.c_str(), b.s.c_str()) <  0;
  }
  friend bool operator!=(const String& a, const String& b) {
    return !(a == b);
  }
  friend bool operator> (const String& a, const String& b) {
    return b < a;
  }
  friend bool operator>=(const String& a, const String& b) {
    return !(a < b);
  }
  friend bool operator<=(const String& a, const String& b) {
    return !(b < a);
  }
  friend bool operator==(const String& a, const char* b) {
    return compare(a.s.c_str(), b) == 0;
  }
  friend bool operator< (const String& a, const char* b) {
    return compare(a.s.c_str(), b) <  0;
  }
  friend bool operator!=(const String& a, const char* b) {
    return !(a == b);
  }
  friend bool operator> (const String& a, const char* b) {
    return b < a;
  }
  friend bool operator>=(const String& a, const char* b) {
    return !(a < b);
  }
  friend bool operator<=(const String& a, const char* b) {
    return !(b < a);
  }
  friend bool operator==(const char* a, const String& b) {
    return compare(a, b.s.c_str()) == 0;
  }
  friend bool operator< (const char* a, const String& b) {
    return compare(a, b.s.c_str()) <  0;
  }
  friend bool operator!=(const char* a, const String& b) {
    return !(a == b);
  }
  friend bool operator> (const char* a, const String& b) {
    return b < a;
  }
  friend bool operator>=(const char* a, const String& b) {
    return !(a < b);
  }
  friend bool operator<=(const char* a, const String& b) {
    return !(b < a);
  }
};

// In C++20, you only need 4
class String {
  string s;
public:
  bool operator==(const String& b) const {
    return s.size() == b.s.size() &&
      compare(s.c_str(), b.s.c_str()) == 0;
  }
  std::weak_ordering operator<=>(const String& b) const {
    return compare(s.c_str(), b.s.c_str()) <=> 0;
  }
  bool operator==(char const* b) const {
    return compare(s.c_str(), b) == 0;
  }
  std::weak_ordering operator<=>(const char* b) const {
    return compare(s.c_str(), b) <=> 0;
  }
};

Now, as we've come to know that the compiler can do tricks with reversible operators in C++20, there are a lot of open opportunities to ambiguties. Let's see some basics and rules of ambiguity resolution. Consider the below source code:

struct foo {
	bool operator == (const foo&) const;
	bool operator != (const foo&) const;
};

bool unequal (foo x, foo y) {
	return x != y;
}

// What will the compiler choose?
// (1) x.operator!=(y) ?
// (2) y.operator!=(x) ?
// (3) !x.operator==(y) ?
// (4) !y.operator==(x) ?

// We can straight out eliminate option (2), as it is not possible to reverse secondary operators.
// The other three candidates are perfect matches.
// To eliminate further, the rule "reversed candidates lose to non-reversed candidates" is put into use.
// So, we can eliminate option (4).
// To eliminate further, the rule "rewritten candidates lose to non-rewritten candidates is applied" is put into use.
// So, we can eliminate option (3) too!

To summarise the rules of operator ambiguity resolution, they are as follows:

  1. Primary operators are the only reversible operators. Secondary operators can only be rewritten in terms of primary operators (due to symmetricity).
  2. Reversed candidates eliminated against non-reversed candidates.
  3. Rewritten candidates eliminated against non-rewritten candidates.

There is also an OOP interfact to three-way comparisons.

std::compare_three_way: Class that overloads templated operator () to perform <=> comparison.

std::compare_three_way c;
std::cout << (c(10, 20) < 0) << '\n'; // true

You can also compare std::string and std::vector using three-way comparisons.

std::vector <int> a {1, 2, 3};
std::vector <int> b {2, 3, 4};
auto c = a;
auto d = c; d.pop_back();
std::string e = "Hello";
std::string f = "World";
std::cout << (a <=> b < 0) << '\n';       // true
std::cout << (a <=> c == 0) << '\n';      // true
std::cout << (a <=> d < 0) << '\n';       // false
std::cout << (b <=> c >= 0) << '\n';      // true
std::cout << (c <=> b < 0) << '\n';       // true
std::cout << (f <=> e < 0) << '\n';       // false
std::cout << ("C++20" <=> e < 0) << '\n'; // true

I believe that the <=> is not completely optimal yet from some negative points I've read about it. There is no reason to not use it though as the optimal concerns are regarding class types. For fundamental and simple types, it is fast and efficient.

In my personal and honest opinion, I find the <=> operator really cool. Although not very impacting to CP, it's still a great feature to have.

>> Templated Lambdas

This was an expected feature right from the time templates and lambdas were introduced. C++14+ supports generic programming with auto usage. C++20 however will allow you to be able to use multiple types on demand in lambdas which, I must say, makes lambdas a lot powerful.

// Syntax
auto func = [] <typename T> () { };

// C++14+
// type of v is not freely available within lambda scope
auto g = [] (auto v) {
	decltype(v) e = f(v);
	// ...
	return e;
};

// C++20
// type of v is freely available for use within lambda scope
auto g = [] <typename T> (T v) {
	T e = f(v);
	// ...
	return e;
};

In my personal and honest opinion, I have mixed feelings about this one. Usage really depends on what you're trying to do but again it's probably useless to CPers. If you use templated data structure implementations, you're probably going to like it, but meh...

>> Modules

Not much to say. This is an alternative to header files. In the long run, the C++ Community wants to get rid of using macros altogether, I think. This is one of the starting moves taken towards accomplishing that. Read more on the linked cppreference page.

>> consteval, constinit and constexpr

Only consteval and constinit are new in C++20. constexpr has been here since C++11 iirc; it, however, is said to have been made more "relaxed".

consteval: specifies that a function is an immediate function, that is, every call to the function must evaluate to a compile-time constant.

consteval int square (int a) {
	if (std::is_constant_evaluated())
		return a * a;
	else
		return 1e9;
}

auto main () -> int32_t {

	constexpr int a = 10;
	std::cout << square(a) << '\n'; // 100
	std::cout << square(9) << '\n'; // 81
	int x = 10;
//	std::cout << square(x) << '\n'; // error: x is not const
	constinit static const int y = 15;
	std::cout << square(y) << '\n'; // 50625
	std::cout << [] (int a) -> int { return (std::is_constant_evaluated() ? a * a : 1'000'000); } (20); // 1000000

	return 0;
}

constinit: an assertion that the variable has static initialization, otherwise the program is ill-formed.

// copied from cppreference
const char *g() { return "dynamic initialization"; }
constexpr const char *f(bool p) { return p ? "constant initializer" : g(); }

constinit const char *c = f(true); // OK
// constinit const char *d = f(false); // error

I'd say that these are pretty useless to CPers. Just using the normal constexpr for light-weight compile-time optimising may help sometimes (such as when used in prime number generation or sieves, etc).

>> Designated Initialisers

It's more easily understood through examples. Go ahead, take a look.

struct foo {
	// class members with default constructions
	int rank         = 0;
	std::string name = "";
	bool active      = false;
	int contribution = 0;
};

union bar {
	int a;
	char b;
};

auto main () -> int32_t {

	// okay, normal default construction
	// not possible in pre-C++20 without writing a default constructor
	foo user41563 (91827, "user41563", false, 50);

	// okay, possible in pre-C++20 and C++20
	foo user41564 {91828, "user41564", false, 50};

	// okay, all designated initialisers used
	foo um_nik { .rank = 1, .name = "Um_nik", .active = true, .contribution = 180 };

	// okay, active = false default construction; the rest designated initialised
	foo arrow = { .rank = 10000, .name = "Arrow" };

	// error: designator order for field `foo::rank` does not match declaration order in `foo`
	// you can do out of order designated initialisation in C but not C++
//	foo tourist { .name = "tourist", .rank = 2 };

	// okay
	bar p;

	// okay
	bar q { .a = 10 };

	// okay
	bar r { .b = 'A' };

	// error: too many initializers for `bar`
	// only one initializer may be provided for a union
//	bar s { .a = 10, .b = 'A' };

	return 0;
}

You're more than likely not going to be using it, simply because it's out of habit.

>> Initialiser statements in range-for loops

Remember this guy?

for (int i = 0; i < n; ++i) { ... }

This was him until a few weeks back.

std::vector <int> v {...};
for (auto i : v) { ... }

And now:

// you can have initialisation in range-for loops now
for (std::vector <int> v {1, 2, 3}; [[maybe_unused]] auto i : v) { }
// maybe_unused is an attribute from C++17
// read more about them on cppreference or elsewhere
// you can write the for loop without it too

I feel like this might just end up being the only "good" feature that CPers end up using, other than the ranges library among other things. It's pretty cool that you can do this now, I think. If you've ever faced scope related naming issues, this might be your knight in shining armour. What I don't understand is why it took 9+ years for the C++ Community to give this to us.

>> Coroutines

There are three new keywords being added in support for coroutines namely: co_await, co_return and co_yield. Coroutines are mostly going to be helpful in multithreading programming AFAIK. I haven't studied much about it and only have a good basic knowledge about multithreading and haven't really written many programs related to the same (other than simple beginner examples). So, I'll leave it that for now; but if you'd like me to write an introductory example and talk a little bit about it, please write so in the comments and I'll get started with some study work on it.
As far as usage in CP goes, if someone more knowledgeable than me in this field writes about it in the comments, I'd be more than willing to add it to the blog.

>> Feature Test Macros

Starting from C++20, you can detect whether or not you will be able to use language features. All of this feature checking is handled with preprocessor macros. Read more about them here.

>> std::span

A span is non-owing view over a container. They are cheap to construct and copy and be considered as something that holds a reference to their data. They can be of static or dynamic extent.

// notice, not captured by reference and yet changes are reflected back in main
// this is because a span holds references to the elements it is used as "view" over
void increment (std::span <int> span) {
	for (int& i : span)
		i += 1;
}

void print (std::span <int> span) {
	for (int i : span)
		std::cout << i << ' ';
	std::cout << '\n';
}

auto main () -> int32_t {

	std::vector <int> v {1, 2, 3, 4, 5};
	std::array <int, 3> a  {41, 68, 56};

	increment(v);
	print(v); // 2 3 4 5 6
	increment(a);
	print(a); // 42 69 57

	return 0;
}
>> <bit>

The <bit> header provides certain bit operations that can be performed on unsigned integer types.

std::rotl(x, s): Rotate x to the left by s bits

std::rotr(x, s): Rotate x to the right by s bits.

std::countl_zero(x): The number of contiguous zero bits in x, starting from the highest bit.

std::countl_one(x): The number of contiguous one bits in x, starting from the highest bit.

std::countr_zero(x): The number of contiguous zero bits in x, starting from the lowest bit.

std::countr_one(x): The number of contiguous one bits in x, starting from the lowest bit.

std::popcount(x): The number of bits set in x.

std::has_single_bit(x): True if x is a power of two, false otherwise.

std::bit_ceil(x): The smallest power-of-two not less than x.

std::bit_floor(x): The largest power-of-two not greater than x.

std::bit_width(x): The smallest integer greater than the base-2 logarithm of x.

Usage examples:

auto main () -> int32_t {

	unsigned int a = 1;
	std::cout << std::boolalpha;
	std::cout << (a = std::rotl(a, 10)) << '\n';                 // 1024
	std::cout << (a = std::rotr(a, 5))  << '\n';                 // 32
	std::cout << std::countl_zero(a) << '\n';                    // 26
	std::cout << std::countl_one(a) << '\n';                     // 0
	std::cout << std::countr_zero(unsigned(1 << 20)) << '\n';    // 20
	std::cout << std::countr_one(unsigned(1 << 20) - 1) << '\n'; // 20
	std::cout << std::popcount(707406378u) << '\n';              // 12
	std::cout << std::has_single_bit(a) << '\n';                 // true
	std::cout << std::bit_ceil(33u) << '\n';                     // 64
	std::cout << std::bit_floor(63u) << '\n';                    // 32
	std::cout << std::bit_width(1u << 16) << '\n';               // 17

	return 0;
}

This will most likely be useful to most users imo. The functionality content in this header will increase with time.

>> <numbers>

Some math constants that are now directly available in the STL. These are:

e, log2(e), log10(e), pi, 1/pi, 1/sqrt(pi), loge(2), loge(10), sqrt(2), sqrt(3), 1/sqrt(3),
Euler-Mascheroni constant egamma and Golden Ration phi.

auto main () -> int32_t {

	std::cout << std::fixed;
	std::cout.precision(30);
	std::cout << std::numbers::e << '\n'          // 2.718281828459045090795598298428
	          << std::numbers::log2e << '\n'      // 1.442695040888963387004650940071
	          << std::numbers::log10e << '\n'     // 0.434294481903251816667932416749
	          << std::numbers::pi << '\n'         // 3.141592653589793115997963468544
	          << std::numbers::inv_pi << '\n'     // 0.318309886183790691216444201928
	          << std::numbers::inv_sqrtpi << '\n' // 0.564189583547756279280349644978
	          << std::numbers::ln2 << '\n'        // 0.693147180559945286226763982995
	          << std::numbers::ln10 << '\n'       // 2.302585092994045901093613792909
	          << std::numbers::sqrt2 << '\n'      // 1.414213562373095145474621858739
	          << std::numbers::sqrt3 << '\n'      // 1.732050807568877193176604123437
	          << std::numbers::inv_sqrt3 << '\n'  // 0.577350269189625731058868041146
	          << std::numbers::egamma << '\n'     // 0.577215664901532865549427242513
	          << std::numbers::phi << '\n';       // 1.618033988749894902525738871191

	return 0;
}
>> <ranges>

C++ Ranges are a really important aspect of C++20 and something called "views" is a big part of it.
The ranges library makes working with range-like containers very easy, convenient and powerful. No more verbose function calls with begin()'s and end()'s needed to apply different algorithms on vectors :)
A Range was conceptualised as something which can adapt to a pair of iterators, just as iterators were conceptualised to adapt to pointers. Range algorithms are lazy and can be easily composed to work directly on containers.
I'm not going to do too much talking about how much I'm in love with views and ranges but rather show you examples to demonstrate its power.

Usage:

// traditionally
std::some_algorithm(container.begin(), container.end(), ...);

// now
std::ranges::some_algorithm(container);

The ranges library was proposed by Eric Niebler and was originally written in verbose SFINAE (called the range-v3 library). With concepts, a lot of the code was simplified and a revised addition was added to the C++20 library.

Some definitions first before diving in.

  • What is a range?: Simply put, a collection of iterable items which support begin() and end().

  • What is a container?: A range that owns its elements. (A std::span is not a container due to this reason; it doesn't own its elements)

If you've been programming for some time in C++, you'd have come across this code very frequently. std::sort(v.begin(), v.end());. What was the reason of choosing this design and not std::sort(v);? It is because doing the former allowed more flexibility to the programmer in an obvious way (and it was also more easily implementable in the library). You could do things like the following:

// sort only those elements after the 10th element
std::sort(v.begin() + 10, v.end());

// sort in reverse order
std::sort(v.rbegin(), v.rend());

Take a look at this comment by Endagorion. Absolute beauty with the well known all(x) macro which expands to (x.begin(), x.end()). Very flexible in usage indeed: sort(x + all(v) - y);!

However, this interface could also lead you into stupid typos causing you a hard time debugging. It is also not very intuitive to understand all the stuff going on with begin()'s and end()'s for beginners in programming. Example:

// it is possible to write this (and you wouldn't get any errors) as a possible typo
// at runtime, your program is going to produce what's called undefined behaviour
std::sort(begin(a), end(b));

C++20 comes up superior in this aspect and area of interface. The algorithms in std::ranges work on containers that are a "range", such as vectors/arrays.

// simple non-decreasing sort
std::ranges::sort(v);

// sort only those elements after the 10th element
std::ranges::sort(std::ranges::views::drop(v, 10));
std::ranges::sort(std::views::drop(v, 10));

// sort in reverse order
std::ranges::sort(std::ranges::views::reverse(v));
std::ranges::sort(std::views::reverse(v));

// you can combine multiple algorithms too; like this:
std::ranges::sort(std::ranges::drop(std::ranges::reverse(v, 10)));

// if you are having trouble understanding the verbose code above, I'll get to explaining it soon
// also, I'm sorry for not using aliasing or dropping std:: in my examples. don't be mad :)

In a comment on my other blog, Everule asked if there is a way to work around using both using namespace std; and using namespace std::ranges; without function name clashes when using algorithms like sort. I don't know if there is an easy work around, if any. If there are, please share in the comments. For now, I'd suggest going with this:

The common reason I've read about most of the C++20 library being written in entirely separate namespaces altogether is to avoid breaking existing codebases.

// note that you can still declare variables with the name 'r'
// similar to this is the alias declaration of std::views as:
// namespace views = std::ranges::views;
namespace r = std::ranges;
namespace v = std::views;
using namespace std;

sort(v.begin(), v.end());
r::sort(v);

What is a view?: A view is a range defined on another range using some algorithm. Views do not own data beyond the algorithm they perform nor after their construction, copy and destruction. The algorithm they perform on the range they are defined on is "lazily evaluated" and can be "combined/piped" with multiple other views.

What is "lazy evaluation"?: You can create a view using some algorithm over your range/container. But, nothing actually happens beyond the construction of the view, not even the algorithm execution. Execution only takes place at the moment you request something from the view, like an element value.

std::vector <int> v {3, 2, 1};
auto view_v = std::views::reverse(v);
// here, we create a view called "view_v" over our container "v" which happens to be a vector of int
// Creating this view doesn't change "v". It doesn't effect "view_v" either; it holds no element.
// The time taken to construct "view_v" and the size it occupies in memory does NOT depend on "v".

// now, let's do something interesting
int front = view_v.front();
// what do you think happens?
//
// front is now equal to 1
// But, more importantly, the reversing and resolving of first element of "v" to the last element
// of "view_v" takes place ON DEMAND.
// If you have an expensive transformation and you perform it multiple times on the same element,
// you're going to have some slow-down.

What is the "combination" of multiple views?: Till now, I believe that you've believed that std::views::reverse(x) is a view. Well, it's not. It is actually what's known as a range adaptor. An adaptor takes a range as its argument (here, a vector) and returns a view object over the range. This feature allows what's called chaining of adaptors using the |(pipe) operator. How it ends up working under the hood? No idea, sorry, except that there is some operator overloading.

// take vector v {1, 2, 3, 4}
// reverse it
// drop the first 3 elements
// v is "piped" into a reverse adaptor which inturn is piped into a drop adaptor, and
// this view object is what ends up getting returned
auto view_v = std::views::reverse(v) | std::views::drop(3);
int front = view_v.front(); // 4

Here is a simple introductory example on how to use ranges and views:

// introductory example
auto main () -> int32_t {

	std::vector <int> v (50);
	// can also use std::views::iota or std::iota_view
	std::iota(begin(v), end(v), 1); // fill the vector like 1 2 3 4 ... 50

	// - usage of range-for with initialisation
	// - usage of view algorithms filter and transform
	// - filter removes all values for which n % 2 == 1 results in false i.e. the even numbers
	// - transform multiplies each of the remaining values by 2
	// - notice how the usage of the `|`(pipe) symbol can be used in succession between
	//   different view algorithms
	// - the order in which you apply view algorithms matter
	//   see next code snippet to understand why by looking at obtained results
	// - the output obtained here is the twice the value of odd numbers in v
	for (auto result = v
		| std::views::filter([] (int n) { return n % 2 == 1; })
		| std::views::transform([] (int n) { return 2 * n; }); auto i : result)
		std::cout << i << ' ';

	// - order of view algorithms matter
	// - the output obtained here is nothing
	//   this is because transform is applied first, which causes all number to become even
	//   and then we filter out the numbers whose remainder modulo 2 not one i.e. all the
	//   even numbers.
	std::cout << '\n';
	for (auto result = v
		| std::views::transform([] (int n) { return 2 * n; })
		| std::views::filter([] (int n) { return n % 2 == 1; }); auto i : result)
		std::cout << i << ' ';

	// - this snippet is exactly the same as the one above this except
	//   that in this we filter out elements whose remainder modulo 2 is one (or in other
	//   words, we take elements that satisfy n % 2 == 0, which in this case is all the
	//   numbers as we transform everything by a factor of 2 first.
	std::cout << '\n';
	for (auto result = v
		| std::views::transform([] (int n) { return 2 * n; })
		| std::views::filter([] (int n) { return n % 2 == 0; }); auto i : result)
		std::cout << i << ' ';

	// note:
	// std::views is an alias to std::ranges::views
	//
	// You too can create your own aliases for simplicity using the syntax:
	// namespace name = some_other_namespace_name;
	//
	// For the sake of convenience, my advice to you is to add these lines to your code when
	// working with ranges (if you don't like typing huge namespace related names in your code)
	// namespace r = std::ranges;
	// namespace v = std::views; // or std::ranges::views

	return 0;
}

Range Adaptors

  • views::all: Creates view over all elements of a range (which could itself possibly be a view).

  • views::filter(satisfy_constraint): Filter out all elements that do not satisfy some constraint you put on them (such as n % 2 == 0).

  • views::transform(func): Transform all elements by applying some function.

  • views::reverse: Reverse a view by returning a view of iterators of opposite sense.

  • views::take(x): Take min(x, range.size()) elements from the range.

  • views::join: Flatten all viewable range of ranges into a single range.

More on adaptors on cppreference or other sources but these are the adaptors I've studied and used in code.

  • std::ranges::subrange: Returns a view with begin() and end() corresponding to the subrange asked for.
// copied from cppreference
#include <iostream>
#include <map>
#include <ranges>
#include <string_view>
 
template <class V> void mutate(V& v) { v += 'A' - 'a'; }
 
template <class K, class V>
void mutate_map_values(std::multimap<K, V>& m, K k) {
    auto iter_pair = m.equal_range(k);
    for (auto& [_, v] : std::ranges::subrange(iter_pair.first, iter_pair.second)) {
        mutate(v);
    }
}
 
int main()
{
    auto print = [](std::string_view rem, auto const& mm) {
        std::cout << rem << "{ ";
        for (const auto& [k, v] : mm) std::cout << "{" << k << ",'" << v << "'} ";
        std::cout << "}\n";
    };
 
    std::multimap<int,char> mm{ {4,'a'}, {3,'-'}, {4,'b'}, {5,'-'}, {4,'c'} };
    print("Before: ", mm);
    mutate_map_values(mm, 4);
    print("After:  ", mm);
}

// Output:
Before: { {3,'-'} {4,'a'} {4,'b'} {4,'c'} {5,'-'} }
After:  { {3,'-'} {4,'A'} {4,'B'} {4,'C'} {5,'-'} }

Ranges are probably one of the best additions to the STL. I don't know much about the efficiency details but I feel this is going to be used a lot among CPers.

>> std::source_location

You don't need to use the __SOME_COMPILER_MACRO__ macros anymore. The STL now provides with a standard way of being able to do that. It is experimental as of GCC 10.2.0 libstdc++ implementation.

#include <iostream>
#include <experimental/source_location>

auto main () -> int32_t {
	std::experimental::source_location location = std::experimental::source_location::current();
	std::cout << location.file_name() << '\n'
	          << location.function_name() << '\n'
	          << location.line() << '\n'
	          << location.column() << '\n';
}

// Output:
C:\Users\aryan\Desktop\Projects\Template\main.cpp
main
5
0
>> Miscellaneous
  • std::to_array(x): Converts a given array or array-like object to a std::array.
constexpr auto a = std::to_array("blog"); // returns std::array <char, 5>
static_assert(a.size() == 5); // if it was "foo" instead of "blog", static assertion fails
for (auto c : a) std::cout << c << ' '; // b l o g

int arr [] = {0, 1, 2, 3, 4};
auto res = std::to_array(arr);
// static_assert(res.size() == 4); // static assertion failed


  • std::bit_cast <T> (x): A safer way to interpret an object from one type to another.
double d = std::numbers::pi;
int i = std::bit_cast <int> (d);


  • std::midpoint(x, y): Calculate the midpoint of two integers safely without overflow. Probable usage in binary search.
// The result of midpoint(x, y) is always rounded towards x. Do keep that in mind.
int mid1 = std::midpoint(1, 10); // 5
int mid2 = std::midpoint(10, 1); // 6


  • std::string::starts_with(x) and std::string::ends_with(y): Can be used to check if a string starts with x and/or ends with y.
std::string s = "foobarbaz";
std::cout << std::boolalpha;
std::cout << s.starts_with("fooba") << '\n'; // true
std::cout << s.ends_with("abaz");            // false


  • contains(x) in associative containers: Can be used to check if the container contains the value x.
std::map <int, int> m {{1, 2}, {3, 4}, {5, 6}};
std::set <int> s {1, 2, 3, 4, 5};
std::vector <int> v {1, 2, 3};

std::cout << std::boolalpha;
std::cout << (s.find(4) != s.end()) << '\n' // true
	  << s.contains(4) << '\n'          // true
	  << m.contains(1) << '\n'          // true
	  << m.contains(6) << '\n'          // false
	  << s.contains(42) << '\n';        // false
//	  << v.contains(3) << '\n';         // error: std::vector <int> has no member name 'contains'


Sources

cppreference
C++20 Wiki
Modernes Cpp
Open-std
Isocpp
Anthony Calandra — Modern Cpp Features
Stack Exchange
Almighty Google Search

Links to a few good tutorials by others

I'm adding a few good C++ related blogs (personal favourite reads) I've come across on CF so that people knew to the language doing CP can have an easy time navigating.
(Apologies if you are irritated by notifications)

C++ Tips and Tricks by Golovanov399
C++17, competitive programming edition by Igorjan94
C++ Tricks by Swift
Catching silly mistakes with GCC by andreyv

Additional

  • It has taken me about 8-10 hours (yes, I'm a slowth typer with an average of only 50 WPM) of typing and collecting old pieces of code, rewriting of code and other preparations with countless hours of prior study to compile this blog on C++20. I don't cover as much as I would like to, even in this gigaginormous blog. If you like it, do suggest me on how I could improve on my explanations and additional C++20 topics you'd like to have more elaboration on. I'd be glad to write more!

  • It is very likely that you will first scroll to the bottom of this blog to see how long it is :)

  • A few predict-the-output questions on ranges:

Q1 What is the output of the following program?

Code
Solution

Q2 What is the output of the following program?

Code
Solution
  • Compilation of source code used in the blog:
Source Code

I hope you enjoyed reading through!

 
 
 
 
  • Vote: I like it
  • +243
  • Vote: I do not like it

»
3 weeks ago, # |
Rev. 2   Vote: I like it 0 Vote: I do not like it

About struct init: get used to constructions like foo arrow = { .rank = 10000, .name = "Arrow" };, they make your code easier to read and check for bugs. Out of order init is bad for the same reason.

I wouldn't trust const... keywords, they're taken more like hints for the compiler and the actual performance of your code depends... well, on that code. You can totally compile a program that prints the address of a variable you declared constexpr. Vice versa, the compiler can automatically detect accidental constants and optimise them out sometimes.

»
3 weeks ago, # |
  Vote: I like it -8 Vote: I do not like it

C++20 is cool and everything but apart from std::view, i think most of the features are not very useful for CP, and most of the things are already achievable by using Macros

I mean all(x) does the job of ranges for most of us, and most of the features are more dev oriented rather than CP

  • »
    »
    3 weeks ago, # ^ |
      Vote: I like it +10 Vote: I do not like it

    wdym literally everything in bit header is super relevant in CP

  • »
    »
    3 weeks ago, # ^ |
      Vote: I like it +11 Vote: I do not like it

    Macros are just dirty substitutes that don't actually "solve" the problem of type safety and a whole lot of other concerns. Now, you've got a standard way of doing things with many more features.

    With a few more updates to the library in the future, a lot more features will be added to support even more powerful operations on ranges, bits, math, etc.

»
3 weeks ago, # |
  Vote: I like it +3 Vote: I do not like it

Iterating over some set of numbers can also be done using an initialsier list. This works with C++17 too.

for(auto x : {1,2,3}){

}
  • »
    »
    3 weeks ago, # ^ |
    Rev. 2   Vote: I like it +3 Vote: I do not like it

    Yes, that's a normal range loop and works below C++20 as well. With C++20 however, you can do things like: for(init_statement; range_declaration : range_expression) { }

    for (int i = 10, j = 20; auto x : v) and for (std::set <int> a (v.begin(), v.end()); auto i : a)

    which was not possible prior to C++20.

»
3 weeks ago, # |
  Vote: I like it -9 Vote: I do not like it

codechef still doesn't have c++17 :)

»
3 weeks ago, # |
  Vote: I like it 0 Vote: I do not like it

Time to learn C++ 17.

»
3 weeks ago, # |
Rev. 2   Vote: I like it 0 Vote: I do not like it

auto main () -> int32_t {

Please gimme any reason for doing like this

  • »
    »
    3 weeks ago, # ^ |
      Vote: I like it 0 Vote: I do not like it

    Style. I like writing my return types as how you could do in lambdas :p