Will Aliens use spaceship operator & other features from C++20??

C++20 Features with Code Examples

Ranges, Coroutines, Spaceship operator, Concepts, Modules, Constexpr, Lambdas & more

Atul Mehra
6 min readSep 18, 2020

--

C++20 has lot of features. What better way to learn to look at code examples of the features like Ranges, Coroutines, concepts, modules, spaceship operator. Why leave new things related to constexpr lambdas behind? If you want a quick introduction & get your mind thinking about what, how & why about new and cool features in it — Then this post is for you.

And the “Brain exercises” are added to get reader in thinking mode about features & invite follow up questions.

Let’s brain thinking more about feature of C++20 and their usage.
Have a look at brain exercises in the post. Feel free to add comments, questions and thoughts.

Have a look and I look forward to your thoughts, ideas & questions in comment section.

Ranges:

“A range is an object conceptually similar to an STL iterator” according to the member of Standard ISO committee and author of range library. We walk through 3 example usages of ranges.

1. Use linux like pipe operator for Function chaining

Eg. Let’s say for a given vector of employee ids, you want to first filter the employees with an Id > 5 and then find next employee id. Here there are multiple operations which can be chained.

Use pipe operator to build complex functionalities.

Code example 1 — Pay special attention to pipe operator on line 4

std::vector myvector{3, 1, 2, 9, 4, 8, 7, 5, 6}; 
auto greater_than_five = [](int i) { return i > 5; };
auto next_number = [](int i) { return i + 1; };
auto filtered = myvector | std::ranges::views::filter(greater_than_five) | std::views::transform(next_number);`

Brain exercise — Let’s think of more realistic use cases?

2. Write simplified code to use with algorithms

Ask a python or javascript programmer about how it feels like reading C++ syntaxes. Below usage of ranges makes writing more expressive code.

std::ranges::sort(myvector);

Brain Exercise — Can we combine this with pipe operator as given in example 1?

3. Work on keys & values of associative containers, chain them together in a single line.

You may want to compare this with how much code you would have to write to achive same before C++20.

std::unordered_map<int, std::string> employees{{3, "Atul"}, {1, "Vishal"}, {2, "Rashid"}, {9, "John"}};auto filtered = employees | std::ranges::views::keys | std::ranges::views::filter(greater_than_five) | std::views::transform(next_number);

4. Lambda — Pass “this” by value to lambda with clearer syntax

C++17 allowed capturing this by value. C++20 allows developer to write first statement below.

[=, this]() { //Do something};   //Error in C++17, but ok in C++20       
[=, *this]() { //Do something };

Brain Exercise — Why & when would a developer like to capture this by value?

Hint: Multithreading & NUMA node friendly coding.

5. Constexpr functions which can allocate memory dynamically

Now we will be able to allocate memory dynamically within a constexpr. This opens possibility of whole new interesting features like reflection, metaprogramming or having containers like vector and maps inside constexpr

Wait a minute! Reflection & containers at compile time!! That’s amazing
Wait a minute! Reflection & containers at compile time!! That’s amazing
constexpr void examplefunction()  
{
int *var_alloc_dynamic = new int(10);
}

From assembly code, we can see that there was no call to function and it happened at compile time.

main:push   rbpmov    rbp,rspmov    DWORD PTR [rbp-0x4],0x0mov    eax,0x0pop    rbpretnop    WORD PTR cs:[rax+rax*1+0x0]xchg   ax,ax

Brain Exercise: As I said that it lays foundation for interesting features like reflection and use of stl containers in constexpr. So let’s keep an eye on these features and examples.

Coroutines

6. Coroutines are targeted to simplify asynchronous coding. They allow functions to be suspended and resumed. Proposal with C++ standard committee introduces standard multithreading patterns like latches, barrier, cancellation of calls and so on.

Example: co_yield in below code makes it a co-routine.

Couroutine_Example_Generator<int> f() {
for (int i = 0; i < 10; ++i) co_yield i;
}int main ()
{
for (auto i = f(); i.move_next(); )
{
std::cout << i.current_value() << ‘ ‘;}
return 0;
}

Brain Exercise: We still need to see the clear advantage of using coroutines over simple std::async or threads. I cover more details probably in the future posts. Let’s keep our curiosity going.

7. Modules — Use python style modules.

Standard committee is aiming to reduce compile time with a different design to handle translation units as they were with the cpp header files. On compiling a module a compiled module interface is generated which is then read by module implementation unit and the importer of the module. Module linkage and mangling rules are also going to be different.

How all these developments impact & benefit the end developer? Let’s await more details and be on the lookout. That’s our Brain Exercise for this recipe.

Meanwhile here’s an example —

import modules_example; 

void Feature_Modules_SimpleModuleExample()
{
SimpleModuleExample();
}
int main(int argc, char *argv[])
{
Feature_Modules_SimpleModuleExample();
}

And a module declared in different cpp file.

module;
export module module_example;
export void SimpleModuleExample(){
std::cout << “Called from SimpleModuleExample” << std::endl;
}

8. Concepts

Constarints & concepts allow rules for template arguments defined such that the calls can match to appropriate functions. These rules could have been written with enable_if as well. With constraints and concepts these become much readable and elaborate rules to be written.

template <class T>
concept ConceptMoveableAndComparable = requires(T a, T b)
{ a == b; a = std::move(b);
};
template <ConceptMoveableAndComparable T>
void SimpleConceptsExample(T)
{ std::cout << "Function is valid " << __func__ << std::endl;
}
//Satisfies constraint defined above
template <typename T>
struct SampleStruct
{
bool operator==(SampleStruct<T> &)
{ return true; };
SampleStruct<T> &operator=(SampleStruct<T> &&) = default; SampleStruct(SampleStruct<T> &&) = default;SampleStruct(const SampleStruct<T> &) = default; SampleStruct() = default;
};
//Does not satisfy constraint defined abovetemplate <typename T>struct SampleStruct2{
bool operator==(SampleStruct2<T> &) { return true; };
//Because of this, SampleStruct2 fails the constraint //defined by ConceptMoveableAndComparable
SampleStruct2<T> &operator=(SampleStruct2<T> &&) = delete; SampleStruct2(SampleStruct2<T> &&) = default; SampleStruct2(const SampleStruct2<T> &) = default; SampleStruct2() = default;};

9. Spaceship operator < = >

Spaceship operator in C++20

It’s very common to have objects that need to be compared. And most likely developer will end up implementing all of ==, !=, <, ≤, >, ≥ operators. That’s a lot of code to write and maintain. C++20 brings in spaceship operator that can handle all these scenarios in a single function. Have a look at example where the class has 3 members. The comparison requires to return value by comparing member1 first, then member2 followed by member3.

struct FeatureThreeWayComparison1
{
public:
FeatureThreeWayComparison1(int a, int b, int c) : mem1{a}, mem2{b}, mem3{c}
{ }
auto operator<=>(const FeatureThreeWayComparison1 &r) const
{ std::cout << "Using 3 way comparison operator " << std::endl;
if (mem1 != r.mem1)
return mem1 <=> r.mem1;
else if (mem2 != r.mem2)
return mem2 <=> r.mem2;
else if (mem3 != r.mem3)
return mem3 <=> r.mem3;
return std::strong_ordering::equivalent;
}
private: int mem1, mem2, mem3;
};
void ThreeWayCompareHelperFunction (FeatureThreeWayComparison1 &obj1, FeatureThreeWayComparison1 &obj2)
{
auto v = (obj1 <=> obj2);
if (v == 0)
{ std::cout << "Objects 1 & 2 are equivalent" << std::endl; }
else if (v > 0)
{ std::cout << "Object 1 > Object 2" << std::endl; }
else
{ std::cout << "Object 1 < Object 2." << std::endl; }
}

10. Bitfield initialization

If you are dealing with bitfields, below is how can you now initialize bitfields. It was an error before C++20. Error “an assignment cannot appear in a constant-expression”

struct example_bitfields{unsigned int b : 8 = 20;};
Don’t forget to smile & leave comments & questions.

Happy CPPing

Atul Mehra

--

--

Atul Mehra

Solution Architect|Techie 16yrs exp| SMEs| Fintech |Social Impact