Your attention is invited to the 11th part of an e-book on undefined behavior. This is not a textbook, as it's intended for those who are already familiar with C++ programming. It's a kind of C++ programmer's guide to undefined behavior and to its most secret and exotic corners. The book was written by Dmitry Sviridkin and edited by Andrey Karpov.
C++ programmer's guide to undefined behavior: part 11 of 11
by Dmitry Sviridkin
From the article:
]]>Developing multithreaded applications is always challenging. The problem of synchronizing access to shared data is a perennial headache. It'd be ideal if we had a well-tested, reliable library of containers, high-level primitives, and parallel algorithms that managed all invariants. It'd be ideal if static compiler checks prevented us from misusing all these things. How nice it would be... Before C++11 and the standardized memory model, we could use threads but only at our risk. Starting with C++11, there are some pretty low-level primitives in the standard library. Since C++17, there are still various parallel versions of algorithms, but we can't even fine-tune the number of threads or their priorities.
The std::optional<T>
is a powerful tool for handling optional values, but assigning non-trivial types like Doodad
to it can lead to unexpected compilation errors. This post explores why such assignments fail and unpacks the nuances of std::optional
and type construction in modern C++.
The Puzzle of Trying to Put an Object into a std::optional
by Raymond Chen
From the article:
]]>The C++ standard library template type
std::
has one of two states. It could be empty (not contain anything), or it could contain aoptional<T> T
.Suppose you start with an empty
std::
. How do you put aoptional<T> T
into it?One of my colleagues tried to do it in what seemed to be the most natural way: Use the assignment operator.
struct Doodad { Doodad(); ~Doodad(); std::unique_ptr<DoodadStuff> m_stuff; }; struct Widget { std::optional<Doodad> m_doodad; Widget() { if (doodads_enabled()) { // I guess we need a Doodad too. Doodad d; m_doodad = d; } } };Unfortunately, the assignment failed to compile:
In this article, we'll learn about -Wstring-conversion
, something I learned from C++ Brain Teasers by Anders Schau Knatten](https://www.sandordargo.com/blog/2024/10/16/cpp-brain-teasers). Clang offers this compiler warning which fires on implicit conversions from C-strings to bool
s.
Implicit String Conversions to Booleans
by Sandor Dargo
From the article:
]]>Let’s start with the first part by explaining why such an implicit conversion is possible. A string literal is an array of
const char
s. Arrays can be converted into pointers, something we talked about last week when we discussed whyspan
s are so useful. This is also called decay. Furthermore, pointers can be converted into booleans. That is how a string literal can be converted into abool
.static_assert(!!"" == true); static_assert(static_cast<bool>("") == true);What might be surprising though is that even an empty string literal is converted into
true
. The reason is that only anullptr
would be converted intofalse
, but an empty string literal is an array of a size of one so it’s not anullptr
. As a result,""
converted totrue
. The possible confusion is that the one character in that array of one is the\0
terminator. But this shouldn’t really matter. You shouldn’t use such shady implicit conversions.We could end this article right here. But life is not ideal and I tried to turn on
-Wstring-conversion
in a production codebase where I found a few different cases of string literals conversions.
While most time zones use simple hour offsets from UTC, some regions have chosen unusual time differences. In this blog post, we’ll explore how we can discover such zones using C++20’s chrono library.
Around the World in C++: Exploring Time Zones with std::chrono
by Bartlomiej Filipek
From the article:
]]>We’ll use GCC 14.2 as it fully supports C++20 chrono and also
std::print
from C++23.First Attempt: Basic Zone Iteration
C++20 introduced comprehensive time zone support through the
<chrono>
library. The implementation relies on the IANA Time Zone Database (also known as the “tz database” or “zoneinfo”), which is the de facto standard for time zone information used by most operating systems and programming languages.The Time Zone Database
In C++20, the time zone database is represented by the
tzdb
class:
C-style arrays are still used, mostly when you have to deal with C-libraries. They come with significant limitations, particularly when passed to functions where array decay occurs, leading to the loss of size information.
Use std::span Instead of C-style Arrays
by Sandor Dargo
From the article:
]]>While reading the awesome book C++ Brain Teasers by Anders Schau Knatten, I realized it might be worth writing about spans.
std::span is a class template that was added to the standard library in C++20 and you’ll find it in the <span> header. A span is a non-owning object that refers to a contiguous sequence of objects with the first sequence element at position zero.
In its goal, a span is quite similar to a string_view. While a string_view is a non-owning view of string-like objects, a span is also a non-owning view for array-like objects where the stored elements occupy contiguous places in memory.
While it’s possible to use spans with vectors and arrays, most frequently it will be used with C-style arrays because a span gives you safe access to its elements and also to the size of the view, something that you don’t get with C-style arrays.
When and why does it come in handy?
Your attention is invited to the tenth part of an e-book on undefined behavior. This is not a textbook, as it's intended for those who are already familiar with C++ programming. It's a kind of C++ programmer's guide to undefined behavior and to its most secret and exotic corners. The book was written by Dmitry Sviridkin and edited by Andrey Karpov.
C++ programmer's guide to undefined behavior: part 10 of 11
by Dmitry Sviridkin
From the article:
]]>As you can see from the backtrace, the problematic object whose destructor caused the crash was a vector of strings in libgtest.so. In GTest sources, I found that this vector is a global variable where InitGoogleTest() stored recognized command line arguments. It's just a global variable declared in the compiled file and not presented in the header file. All seemed fine, except for one detail: it wasn't marked as static and was not wrapped in an anonymous namespace. So what? It worked, didn't it? Yeah, it worked. The trick was how the gMock library is built. Let's reproduce it step by step.
In this blog post, we will explore handling dates using std::chrono, including time zones. We’ll utilize the latest features of the library to retrieve the current time across various time zones, taking into account daylight saving time changes as well. Additionally, we will incorporate new capabilities introduced in C++23, such as enhanced printing functions and more.
What is the Current Time Around the World? Utilizing std::chrono with Time Zones in C++23
by Bartlomiej Filipek
From the article:
]]>Let’s start with the following code that prints the current date and time:
#include <chrono> #include <print> int main() { auto now = std::chrono::system_clock::now(); std::print("now is {}", now); }You can run it through Compiler Explorer
In my session, I’m getting the following results:
Now is 2024-11-01 11:44:06.374573703
Recent versions of the C++ language (C++20 and C++23) may allow you to change drastically how you program in C++. I want to provide some fun examples.
Having Fun with Modern C++
by Daniel Lemire
From the article:
Thanks to the integration of the features from the popular fmt library, it is much easier to format strings elegantly in C++. In turn the fmt library was inspired by the work done in languages like Python.
Suppose that you have a vector of integers and you want to print its content:
std::vector<int> v = {1, 2, 3, 4, 5}; std::println("{}", v);
]]>Suppose you want it to be centered in a line of 40 characters, with underscore characters around it:
std::vector<int> v = {1, 2, 3, 4, 5}; std::println("{:_^40}", v); // ____________[1, 2, 3, 4, 5]_____________
Last week, I got the chance to go to Berlin and participate in the 10th edition of Meeting C++ which is as far as I know the biggest C++ conference in Europe. Considering both the online and onsite participants, there were about 500 of us, eager to learn more mostly but not only about C++.
Trip Report: Meeting C++ 2024
by Sandor Dargo
From the article:
]]>The conference was held in the East side of Berlin, in the Vienna House by Wyndham Andel’s Berlin Hotel offering some cool views of the city from its higher floors, especially from its sky bar. Not that we had a lot of time wandering in Berlin, the schedule was quite packed. Although the program only started around 9-10 AM depending on the day, the first two days the official program didn’t end before 10 PM.
Given the bit cold and cloudy weather, I realized that Berlin in November is really perfect for a conference. People will not be tempted to escape the venue for long walks outside. It just feels better to be inside.
Programming at compile time has been possible in C++ for a long time. Wu Yongwei considers its past, present and future.
Compile-time programming is a key feature of C++. It enables writing high-performance code often unattainable in other languages. This article explores its past, present, and future applications, highlighting the diverse possibilities in C++. We’ll briefly cover template metaprogramming, constexpr, variadic templates, static reflection, and more.
C++ Compile-Time Programming
by Wu Yongwei
From the article:
]]>Compile-time programming is vastly different from run-time programming. The code runs during compilation, but the results can be used at run time.
Some believe compile-time programming is merely a trick, unused in real-world engineering. To them, I ask: do you use the C++ Standard Library? The mainstream implementations rely heavily on various programming techniques, including compile-time programming.
‘I don’t write the standard library’ – this might be a possible response. But consider this: the standard library is just one tool, a standard weapon. Is it enough to use only standard tools? That’s the real question.
The abundance of excellent open-source libraries suggests otherwise. A skilled programmer crafts tools for themselves and their team. If your work feels tedious, perhaps it’s time to build a bulldozer to tackle problems.
Compile-time programming offers a way to build such tools.
Probably the two most useful features added to C++20 are requires
and requires
. They make it so much easier to control overload resolution, and when combined with if constexpr
in C++17, they allow basic reflection-based optimizations in templates. While requires requires
has gotten a lot of (negative?!) press for controlling overload resolution, its cousin requires { requires }
is a bit overlooked.
if constexpr requires requires { requires }
by Jonathan Müller
From the article:
C++20 added requires
, a way to enable or disable a function overload based on some compile-time condition. For example, consider a facility for producing debug output of types for error reporting:
]]>The two overloads with the
requires
clause are only enabled for integers or floating point types, respectively, and are not considered otherwise. Additionally, overload resolution is smart: It knows that we want the overload with the most specific requirements, and it will only pick the first function when no other overload matches. This is also whereconcept
comes in: Aconcept
is simply a way to name a group of requirements that affects the search for more specific requirements. The technical term for that is subsumption. Because creating named requirements withconcept
also comes with additional syntax sugar, you don't needrequires
—so this blog post is gonna ignoreconcept
. In general, if you would use aconcept
in only one place, it is too early to introduce it.
Your attention is invited to the ninth part of an e-book on undefined behavior. This is not a textbook, as it's intended for those who are already familiar with C++ programming. It's a kind of C++ programmer's guide to undefined behavior and to its most secret and exotic corners. The book was written by Dmitry Sviridkin and edited by Andrey Karpov.
C++ programmer's guide to undefined behavior: part 9 of 11
by Dmitry Sviridkin
From the article:
]]>And you could write it only in C++. In C, however, this mess has been forbidden (see 6.5.3.2, note 104). Also, you can't use the dereference operator on invalid and null pointers anywhere in C. Meanwhile, C++ has its own, special way of doing things. These weird examples were built in constexpr context (let me remind you that UB is forbidden there, and the compiler checks for it).
One of the reasons that I’m excited for Reflection in C++ is that it can permit you to implement, as a library, many things that previously required language features. In this post, I’m going to walk through implementing P2786R8 (“Trivial Relocatability For C++26”).
Implementing Trivial Relocation in Library
by Barry Revzin
From the article:
]]>The goal here is not to say that the design is right or wrong (although the syntax certainly is suspect), but rather to show the kinds of things that reflection can solve.
We’ll just go straight to the wording and translate it into code as we go:
Trivially Relocatable Types
Scalar types, trivially relocatable class types (11.2 [class.prop]), arrays of such types, and cv-qualified versions of these types are collectively called trivially relocatable types.
This sure sounds like a type trait! Except in the world of reflection, those are just functions. How would we implement such a thing? We could start by doing this:
consteval auto is_trivially_relocatable(std::meta::info type)
-> bool
{
type = type_remove_cv(type);
return type_is_scalar(type)
or (type_is_array(type)
and is_trivially_relocatable(
type_remove_all_extents(type)
))
or is_trivially_relocatable_class_type(type);
}This is a fairly literal translation, where
is_trivially_relocatable_class_type
is something to be written shortly. But one interesting thing about thetype_remove_all_extents
type trait (i.e.std::remove_all_extents
) is that it also works for non-array types, just returning back the same type.
How do you minimise locking between producers and consumers? Lucian Radu Teodorescu describes a common, but currently undocumented, design pattern. Design patterns can help us reason about code. They are like algorithms that are vaguely defined in the code. Once we recognise a pattern, we can easily draw conclusions about the behaviour of the code without looking at all the parts. Patterns also help us when designing software; they are known solutions to common problems.
In this article, we describe a concurrency pattern that can’t be found directly in any listing of concurrency patterns, and yet, it appears (in one way or another) in many codebases. It is useful when we have producers and consumers that run continuously, and we want to minimise the locking between them.
The Publish Pattern
by Lucian Radu Teodorescu
From the article:
Let’s say we have an open-world game. As the player walks through the world, we load the data corresponding to the regions around the player. We have two types of workloads in our scenario: one for loading the data and another for displaying the loaded data. For the sake of our discussion, let’s say that each of these two activities is bound to a thread.
The problem we are trying to solve is how to structure the passing of data (let’s call this document) from the loading thread (producer) to the rendering thread (consumer). This is analogous to the classical producer-consumer problem [Wikipedia-1], but it has some interesting twists. Let’s try to outline the requirements of the problem:
- (R1) The producer is constantly producing new versions of the document.
- (R2) The consumer is constantly consuming document data.
- (R3) The consumer will use the latest version of the document.
Please note that we are discussing multiple versions of the document. In our example, the loading thread will produce different documents depending on the position of the player, and the rendering thread will display the latest document, corresponding to the player’s most recent position.
]]>
Your attention is invited to the eighth part of an e-book on undefined behavior. This is not a textbook, as it's intended for those who are already familiar with C++ programming. It's a kind of C++ programmer's guide to undefined behavior and to its most secret and exotic corners. The book was written by Dmitry Sviridkin and edited by Andrey Karpov.
C++ programmer's guide to undefined behavior: part 8 of 11
by Dmitry Sviridkin
From the article:
]]>To avoid issues, use conditional noexcept always and everywhere and carefully check every function you use. Or don't use noexcept at all. In the second case, however, it's worth remembering that both move operations and swap should be marked as noexcept (and really be noexcept!) to effectively work with standard containers. Don't forget to write negative tests. You may miss a false noexcept and get std::terminate in the release build without them.
A post I wrote back in 2023 When an empty destructor is required resulted in feedback that I'd like to address in today's post.
Smart Pointers and the Pointer to Implementation Idiom
by Andreas Fertig
From the article:
In the 2023 post, I briefly mentioned PImpl idiom. I did not intend to make it the theme of the post. However, I got various questions about PImpl and smart pointers.
The goal of PImpl is to hide implementation details from clients. Since you can declare a pointer of an unknown class, you can shift the entire implementation of such an anonymous class into a
.cpp
file. That way, no client can see any details. Another benefit is that changes to that.cpp
file result in only minor recompiles. Maintaining the same in a header file would cause all.cpp
files, including this header, to recompile. At least the speed-up part is since C++20's modules are no longer necessary. And as long as you don't want to hide classified implementation in the.cpp
file modules, it also gives you the ability to mark constructs as private.
]]>
Your attention is invited to the seventh part of an e-book on undefined behavior. This is not a textbook, as it's intended for those who are already familiar with C++ programming. It's a kind of C++ programmer's guide to undefined behavior and to its most secret and exotic corners. The book was written by Dmitry Sviridkin and edited by Andrey Karpov.
C++ programmer's guide to undefined behavior: part 7 of 11
by Dmitry Sviridkin
From the article:
]]>In the early '70s, Ken Thompson, Dennis Ritchie, and Brian Kernighan worked on the first versions of C and Unix. They made a decision that resonates with pain, suffering, bugs, and inefficiency today, 50 years later. They decided that developers were to write strings—variable-length data—in a sequence that terminated with a null character. Assembly has it, and C should have it too, if people call it "high-level assembly"! After all, the poor old PDP has limited memory: it's better to have one extra byte per string than 2, 4, or even all 8 bytes (depending on the platform) to store the size... Nah, it's better to have a byte at the end! But even other languages store a size, a reference, or a pointer to the data...
std::expected is a powerful feature introduced in C++23 that offers a modern, type-safe alternative to traditional error-handling methods. std::expected allows developers to represent a value or an error in a single object, simplifying the handling of success and failure scenarios in a clean and readable way.
The Definitive Guide to std::expected in C++
John Farrier
From the article:
]]>std::expected is an addition to the C++ standard library that provides a way to return and propagate errors without using exceptions. It represents a value or an error and is similar to the Result type in Rust. This guide covers its use, benefits, and comparison with other error handling techniques, helping developers to write more robust and readable code.
A common question that is often asked is whether C++ STL string objects (like
) or string views (e.g. std::wstring_view) should be passed as string parameters at Windows C-interface API boundaries. The following articles tries to shed some light on this subject, answering that question:std::string
/wstring
Passing C++ STL Strings vs. String Views as Input Parameters at the Windows C API Boundary
by Giovanni Dicanio
From the article:
Passing STL std::[w]string objects at Win32 API boundaries is common for C++ code that calls into Win32 C-interface APIs. When is it safe to pass *string views* instead?
(...) the code will work fine. In fact, the wstring::c_str() method is guaranteed to return a null-terminated C-style string pointer.
On the other hand, if you pass a string view like std::wstring_view in that context, you’ll likely get some subtle bugs! Try experimenting with the above API and something like “Connie is learning C++” and string views!
]]>
Exploring how different languages solve the same problem often reveals interesting contrasts, especially when it comes to implementing powerful features like reflection. While C++26 aims to introduce introspection and code generation via P2996 and P3294, Rust’s approach using its derive macros offers a mature solution for code generation, even without introspection, highlighting different philosophies in language design and their practical applications.
Code Generation in Rust vs C++26
by Barry Revzin
From the article:
]]>One of the things I like to do is compare how different languages solve the same problem — especially when they end up having very different approaches. It’s always educational. In this case, a bunch of us have been working hard on trying to get reflection — a really transformative language feature — into C++26. Fundamentally, reflection itself can be divided into two pieces:
- Introspection — the ability to ask questions about your program during compilation
- Code Generation — the ability to have your code write new code
P2996 (Reflection for C++26) is the (huge) core proposal that fundamentally deals with the first problem, along with setting the foundation for being able to extend this feature in lots of different directions in the future, including generation (for which our design is P3294). But introspection, while valuable, is only half of the piece. Andrei Alexandrescu went so far as to claim in his CppCon talk that introspection without generation is useless.
In C++20, the standard library introduced new synchronization primitives: std::latch and std::barrier. These are the utilities designed to coordinate between concurrent threads.
Synchronization Primitives in C++20
by Shivam Kunwar
From the article:
]]>What is a synchronization primitive?
In concurrent programming, synchronization primitives are the fundamental tools that help in managing the coordination, execution order, and data safety of multiple threads or processes that run concurrently.
Briefly said, they ensure that:
- multiple threads don’t simultaneously execute some specific segment of code (a “critical section”)
- the program and the data remain in a consistent state
- deadlocks (where threads wait indefinitely for resources) and race conditions (where the outcome depends on the timing of accessing the shared data by a thread) are prevented or managed
There are multiple synchronization primitives in C++; for example, mutual exclusion, condition variables, atomic operations, locking mechanisms, etc.
In C++20, we have two additional synchronization primitives: latches and barriers.
Let’s discuss both of them.
std::latch
A std::latch is a synchronization primitive that permits a certain number of count_down operations (decrements) before allowing one or more threads to pass the wait point. A latch cannot be reused once its internal counter reaches zero.
How do we use a latch?
- A std::latch object is created with an initial count.
- Multiple threads can decrement this count using the count_down method.
- Threads can call wait, which block until the internal count of the latch reaches zero.
When you transition from older C++ standards like C++11 or C++14 to the latest C++17 and C++20 it can be a tough journey. It's essential for writing clean and easy-to-maintain code, but many developers find the process challenging.
Write Modern Code with Features of C++17 and C++20
by Andreas Fertig
From the article:
Here are three common hurdles:
1. Variety of New Features
Adapting to C++17 and C++20 can be intimidating. As the latest standards offer a wide range of new features and syntax changes. One such feature is the introduction of structured bindings in C++17, which represents a shift from how variables were traditionally declared and accessed.Old Way (C++14 and Earlier):
Before C++17, when you needed to unpack values from a pair or a tuple, you would typically do something like this:
Here, you need to use std::get<>()
to access each element of the tuple, which is both verbose and less intuitive, especially when dealing with more complex data structures.
]]><img alt="ACCU" data-cke-saved-src="https://isocpp.org/files/img/logo.png" src="https://isocpp.org/files/img/logo.png" 225px;="" height:="" 60px;="" float:="" right;"="" style="float: right;">Programming at compile time has been possible in C++ for a long time. Yongwei Wu considers its past, present, and future.
C++ Compile-Time Programming
by Yongwei Wu
From the article:
Compile-time programming is a key feature of C++. It enables writing high-performance code often unattainable in other languages. This article explores its past, present, and future applications, highlighting the diverse possibilities in C++. We’ll briefly cover template metaprogramming, constexpr, variadic templates, static reflection, and more.
]]>
Converting Unicode strings to lower and upper cases should be an easy task to accomplish. Unfortunately, that is not the case with C++. Even the so common approach of iterating "char-by-char" in the input string and invoking std::tolower/toupper is wrong. Let's discuss a possible solution to this problem in this article:
How To Convert Unicode Strings to Lower and Upper Case in C++
by Giovanni Dicanio
From the article:
]]>[...] A possible solution to properly convert Unicode strings to lower and upper cases in Windows C++ code is to use the LCMapStringEx Windows API. This is a low-level C interface API.
I wrapped it in higher-level convenient reusable C++ code, available here on GitHub. I organized that code as a header-only library: you can simply include the library header, and invoke the ToStringLower and ToStringUpper helper functions.
When working with C++ standard containers and functions, handling references can sometimes lead to unexpected behavior, particularly with copy semantics. This is where std::ref
and std::cref
come into play, allowing you to store references in containers and pass them safely to template functions like std::bind
or std::thread
.
What is std::ref?
by Sandor Dargo
From the article:
]]>Have you heard about
std::ref
andstd::cref
? The helper functions that generate objects of typestd::reference_wrapper
? The answer is probably yes. In that case, this article is probably not for you. But if you haven’t heard about them, or the only usage ofstd::reference_wrapper
you faced was storing references in a vector, then probably it’s worth reading on.This article is inspired by some failing tests that needed me to use
std::ref
in order to pass them.What does
reference_wrapper
do?A reference of an object
T
(T&
) is not copy assignable. On the other hand,std::reference_wrapper<T>
which emulatesT&
it both copy-constructible and copy-assignable. It’s even trivially copyable, so copying can take place on a byte level which makes it very efficient.So when should we use such a wrapper?
When designing a circular doubly-linked list, the initial challenge is determining how to manage the construction of new nodes in relation to existing ones. While constructors seem like a natural fit for placing nodes before or after a given node, overloading them can lead to ambiguity and poor design choices. Instead, using distinct tag types or factory methods provides clearer intent, ensuring flexibility while respecting the constraints of guaranteed copy elision for node addresses.
Constructing Nodes of a Hand-made Linked List, How Hard Can it Be?
by Raymond Chen
From the article:
]]>Suppose you are writing your own circular doubly-linked list structure.
struct node { node* prev; node* next; };A natural choice for the default constructor is to make the node the sole element of a circular doubly-linked list.
struct node { node* prev = this; node* next = this; };What if you also want to add a node after an existing node? Well, we could add a constructor for that.
struct node { node* prev = this; node* next = this; node() = default; // Construct a node after a specific node node(node* other) : prev(other), next(other->next) { prev->next = this; next->prev = this; } };(Note that the “construct after another node” constructor takes the other
node
by pointer, rather than by reference, so that it won’t be mistaken for a copy constructor.)But maybe you also want to have a “before” constructor that inserts the new node before an existing node...
Previously, I tried to answer the question: what’s so hard about constexpr
allocation?. Today, we continue what will become a series of posts about attempting to explain the issues behind a bunch of hard problems we’re trying to solve. The next problem: class types as non-type template parameters.
What's So Hard About Class Types as Non-type Template Parameters?
by Barry Revzin
From the article:
Before C++20, the only types you could use as non-type template parameters were scalar types (like
int
andenum
s, but not floating point), pointers (including pointers-to-members), and references. Notably, not class types.Floating point types were also added in C++20.
The hard question about handling class types is: how do you determine template argument equivalence? Class types can define their own equality. But you don’t really want to rely on that, since now matching specializations becomes a quadratic problem — you can’t really do much when determining the instantiation for some template
f<x>
other than looping through every other valuevi
to see ifx == vi
.The other problem is we need
==
to really be strong enough. One very easy problem to run into in this space is violating the One Definition Rule (ODR). One essential, core requirement for templates is that instantiating the same template with the same arguments has to yield the same code.
]]>
C++ On Sea took place in Folkestone again in February this year. Sandor Dargo shares an overview of his favourite talks and some emergent ideas.
Trip report: C++ On Sea 2024
by Sandor Dargo
From the article:
]]>Last week, between the 3rd and 5th of July, I had the privilege of attending and presenting at C++ on Sea 2024 [CPPoS-1] for the 5th time in a row! I’m grateful that the organizers accepted me not simply as a speaker, but that they granted me a double slot to deliver a half-day workshop about how to reduce the size of a binary. I’m also thankful for my management that they gave me the time to go to Folkestone and share our knowledge on binary size. Last but not least, great thanks goes to my wife, who took care of the kids alone that week.
Let me share with you a few thoughts about the conference.
First, I’m going to write about the 3 talks that I liked the most during the 3 days, then I’m going to share 3 interesting ideas I heard about and then I’m going to share some personal impressions about the conference.
In my previous article on arrays, some readers expressed concern that std::array might be slower than the built-in C array. Several sources of truth exist on this matter, and today we'll go through each of them. Let's first find out what the standard states about it, then look at the std::array implementations in libc++ and libstdc++, and finally look at the assembler of some operations on these objects. Oh, and we'll top it off with benchmarking, of course.
std::array in C++ isn't slower than array in C
by Anton Tretyakov
From the article:
]]>Let's get to the bottom of this. LLVM has a hardening mechanism called _LIBCPP_HARDENING_MODE. We can use it to enable additional checks depending on the mechanism level, which has a total of four levels. Enabling the weakest one removes the checks from the code. In other cases, there may or may not be a check, depending on the check and the level of the mode. We'll prove it. To understand what expands to what, we need to look at the source code. There, we see that depending on the given value of _LIBCPPP_HARDENING_MODE, _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS may expand to _LIBCPPP_ASSERT.
Atomics form a relatively low level, but fundamental part of sharing data across threads. Lucian Radu Teodorescu reminds us what atomics are and how and when to use them.
In an Atomic World
by Lucian Radu Teodorescu
From the article:
]]>We often discuss mutexes as the basic building blocks of concurrency. However, there are more fundamental concepts upon which concurrent programs and synchronization primitives are constructed. The C++ language defines a memory model, which describes how programs behave when multiple threads are involved. Additionally, C++ introduces atomic operations that serve as foundation for working with data across threads, ensuring both safety and performance. The goal of C++ atomics is to closely align with the hardware and eliminate the need for lower-level operations that must work across threads.
The topic of atomics is often overlooked, and the prevailing advice is to avoid them. While this advice is generally sound, there are occasions when we need to use atomics to fully leverage the language’s capabilities. This article aims to give atomics the attention they deserve, as they have yet to be featured in an Overload article.
The subject of atomics is extensive. For a comprehensive exploration, readers are encouraged to consult books by Anthony Williams [Williams19] and Mara Bos [Bos23]. While the Bos book primarily focuses on Rust, there is still much to be learned about atomics for C++ programmers. The reader can also consider cppreference.com for a quick reference to the atomics library [cppreference-1] In this article, we will examine various memory ordering models and illustrate their usage through simplified practical examples.
Your attention is invited to the sixth part of an e-book on undefined behavior. This is not a textbook, as it's intended for those who are already familiar with C++ programming. It's a kind of C++ programmer's guide to undefined behavior and to its most secret and exotic corners. The book was written by Dmitry Sviridkin and edited by Andrey Karpov.
C++ programmer's guide to undefined behavior: part 6 of 11
by Dmitry Sviridkin
From the article:
]]>I/O streams have other flags that represent the state of the stream: whether there were errors, whether we reached the end. Many people know that you can check whether an operation was successful by putting a stream object into a conditional statement (or any context where it is converted to bool). Those unfamiliar with it might use the while (!iss.eof()) check that will one day lead to the infinite loop issue. This happens when the file isn't finished, but can no longer be read—say, if the file is on a network drive, and the network has gone down. Well, that's a story for another time. Let's focus on the correct way to check readability.
In C++, it's common to use RAII types like std::lock_guard
to manage synchronization primitives, ensuring a lock is acquired at object creation and released at destruction. However, a less common but useful pattern is the "anti-lock," which temporarily releases a lock and reacquires it later, useful in scenarios where you need to drop a lock while performing certain operations, like calling out to other components to avoid deadlocks.
Temporarily Dropping a Lock: The Anti-lock Pattern
by Raymond Chen
From the article:
]]>There is a common pattern in C++ of using an RAII type to manage a synchronization primitive. There are different versions of this, but they all have the same basic pattern:
- Creating the object from a synchronization object: Locks the synchronization object.
- Destructing the object: Unlocks the synchronization object.
These types go by various names, like
std::
,lock_ guard std::
, orunique_ lock std::
, and specific libraries may have versions for their own types, such as C++/WinRT’scoped_ lock winrt::
and WIL’sslim_ lock_ guard wil::
(which you thankfully never actually write out; just userwlock_ release_ exclusive_ scope_ exit auto
).One thing that is missing from most standard libraries, however, is the anti-lock.
The idea of the anti-lock is that it counteracts an active lock.
JSON is a widely-used format for data exchange, but in C++, handling JSON efficiently can be challenging. While current solutions like simdjson offer high-speed processing, upcoming features in C++26, such as powerful reflection, promise to simplify and accelerate the serialization and deserialization of JSON, making it both faster and more convenient for developers.
Reflection-based JSON in C++ at Gigabytes per Second
by Daniel Lemire
From the article:
]]>JSON (JavaScript Object Notation) is a popular format for storing and transmitting data. It uses human-readable text to represent structured data in the form of attribute–value pairs and arrays. E.g., {"age":5, "name":"Daniel", toys:["wooden dog", "little car"]}. Ingesting and producing JSON documents can be a performance bottleneck. Thankfully, a few JSON parsers such as simdjson have shown that we can process JSON at high speeds, reaching gigabytes per second.
However, producing and ingesting JSON data can remain a chore in C++. The programmer often needs to address potential errors such as unexpected content.
Yet, often, the programmer only needs to map the content to and from a native C/C++ data structure.
We’ve seen formatting for simple classes and more complicated types. Spencer Collyer finishes his series by showing us how to apply specific formatting to existing classes.
User-Defined Formatting in std::format – Part 3
by Spencer Collyer
From the article:
]]>In the previous articles in this series [Collyer24a], [Collyer24b] I showed how to write classes to format user-defined classes and container classes using the
std::format
library.In this article I will show you how to create format wrappers, special purpose classes that allow you to apply specific formatting to objects of existing classes.
A note on the code listings: The code listings in this article have lines labelled with comments like
// 1
. Where these lines are referred to in the text of this article it will be as ‘line1
’ for instance, rather than ‘the line labelled// 1
’.Format wrappers
I’d now like to introduce a type of class which I call ‘format wrappers’. A format wrapper is a very simple class which wraps a value of another type. They exist purely so that we can define a
formatter
for the format wrapper. The idea is that theformatter
will then output the wrapped value using a specific set of formatting rules. Hopefully this will become clearer when we discuss theQuoted
format wrapper later.A format wrapper is a very simple class, which normally consists of just a constructor taking an object of the wrapped type, and a public member variable holding a copy or reference to that value. They are intended to be used in the argument list of one of
std::format
’s formatting functions, purely as a way to select the correctformatter
.
Your attention is invited to the fifth part of an e-book on undefined behavior. This is not a textbook, as it's intended for those who are already familiar with C++ programming. It's a kind of C++ programmer's guide to undefined behavior and to its most secret and exotic corners. The book was written by Dmitry Sviridkin and edited by Andrey Karpov.
C++ programmer's guide to undefined behavior: part 5 of 11
by Dmitry Sviridkin
From the article:
]]>However, all this fuss with removing and adding const anywhere in the code eliminates this set of optimizations. So, a repeated access by a constant reference to the same data member or member function doesn't need to be cached at all. Note. It's worth mentioning that programmers have unrealistic expectations about the compiler optimizing code when they add more const. Here's a good note on the topic: "Why const Doesn't Make C Code Faster".
In previous posts, we've explored relocation, trivial relocation, and their use in optimizing data structures like vector-like containers. We've also examined how trivial relocation relates to move assignments, enabling further optimization of operations like swaps and algorithms such as std::sort
and std::rotate
.
Qt and Trivial Relocation (Part 5)
by Giuseppe D'Angelo
From the article:
]]>Is trivial relocation allowed in Standard C++?
That’s probably a question we should have asked as soon as we started this journey. Of course, the answer is no, it is not allowed!
Remember how trivial relocation works: we use memcpy a source object(‘s representation) into some storage, and claim that operation realizes the equivalent of move-constructing the source into that storage, plus destroying the source.
The problem is that one can’t just put data into some storage and pretend that an object exists in there. This is only allowed for a specific set of types, such as trivially copyable types. (Note that if a type is trivially copyable, then Qt automatically considers it trivially relocatable.)
However, as we have discussed, many interesting types (QString, std::vector, std::unique_ptr, …) are not trivially copyable, but they would still benefit from trivial relocatability.
Previously, we explored a basic implementation of unique_ptr
in "Understanding the Inner Workings of C++ Smart Pointers - The unique_ptr." Now, let's enhance that model by incorporating a custom deleter, similar to what the Standard Library provides.
Understanding the Inner Workings of C++ Smart Pointers - The Unique_ptr with Custom Deleter
by Andreas Fertig
From the article:
Let's first establish why somebody would want a custom deleter.
One example is that the object was allocated via a local heap, and such must be returned by calling the corresponding deallocation function.
Another example is
fopen
. This function returns aFILE*
object that you are supposed to delete by callingfclose
. A classic job for a unique pointer. But you cannot calldelete
on theFILE
pointer.Here are two examples of using a
void MyDeleter(Object* ptr)unique_ptr
with a custom deleter.
{
delete ptr;
}
unique_ptr<Object> alfred{new Object{}};
static_assert(sizeof(alfred) == sizeof(void*));
unique_ptr<Object, decltype(MyDeleter)> robin{new Object{}, &MyDeleter};
static_assert(sizeof(robin) == sizeof(void*) * 2);
Oh yes, the first object, alfred, doesn't provide a custom deleter. Only robin does. Behind the curtains, both do. Let's look at a modified unique_ptr implementation that handles the custom deleter case.]]>
After reading this article, you should understand the basics of the opaque pointer pattern and how you can implement it using std::unique_ptr
. I also gave some hints on when it is appropriate to use it and when maybe not.
Opaque Pointer Pattern in C++
by Daniel Sieger
From the article:
The basic problem is that C++ class declarations expose private details of the class. Private member functions and data members need to be declared in the header. Here’s an example for illustration:
// Point.h
class Point
{
public:
Point(float x, float y);
float x();
float y();
private:
float x_;
float y_;
};
]]>While users of this class don’t have direct access to the private data members x_ and y_, there is still a dependency: If you change the private implementation details of Point, all other compilation units that include Point.h know about the change and need to be re-compiled.
This only gets worse for more complex dependency chains, e.g., when there are dependencies to other classes internal to the module that need to be included. To a certain degree, this can be dealt with by using forward declarations. However, at the end of the day there is an information leak: Private implementation details are leaking to clients. This goes directly against the idea of information hiding.
The opaque pointer pattern helps to deal with this problem.
The evolution of the C++ language continues to bring powerful features that enhance code safety, readability, and maintainability. Among these improvements, we got changes and additions to enum class functionalities across C++17, C++20, and C++23. In this blog post, we’ll explore these advancements, focusing on initialization improvements in C++17, the introduction of the using enum keyword in C++20, and the std::to_underlying utility in C++23.
Enum Class Improvements for C++17, C++20 and C++23
by Bartlomiej Filipek
From the article:
]]>Before diving into the enhancements, let’s briefly recap what enum class is. An enum class (scoped enumeration) provides a type-safe way of defining a set of named constants. Unlike traditional (unscoped) enums, enum class does not implicitly convert to integers or other types, preventing accidental misuse. Here’s a basic example:
#include <iostream>
enum class Color {
Red,
Green,
Blue
};
int main() {
Color color = Color::Red;
if (color == Color::Red)
std::cout << "The color is red.\n";
color = Color::Blue;
if (color == Color::Blue)
std::cout << "The color is blue.\n";
// std::cout << color; // error, no matching << operator
// int i = color; // error: cannot convert
}
The C++ language has two large categories of “don’t do that” known as undefined behavior and ill-formed program. What’s the difference?
The Difference Between Undefined Behavior and Ill-formed C++ Programs
by Raymond Chen
From the article:
]]>The C++ language has two large categories of “don’t do that” known as undefined behavior and ill-formed program. What’s the difference?
Undefined behavior (commonly abbreviated UB) is a runtime concept. If a program does something which the language specified as “a program isn’t allowed to do that”, then the behavior at runtime is undefined: The program is permitted by the standard to do anything it wants. Furthermore, the effect of undefined behavior can go backward in time and invalidate operations that occurred prior to the undefined behavior. It can do things like execute dead code. However, if your program avoids the code paths which trigger undefined behavior, then you are safe.
In a recent talk at C++OnSea, Arne Mertz highlighted common misuses of guidelines, including the Rule of Five. This discussion prompted me to reflect on a recurring pattern I've observed in C++ classes that explicitly default constructors and destructors, leading to unexpected behaviors with move semantics.
Once More About the Rule of 5
by Sandor Dargo
From the article:
Let’s first repeat what the rule of 5 says.
The Rule of Five tells us that if we need to define any of a copy constructor, copy assignment operator, move constructor, move assignment operator or destructor then we usually need to define all five.
Fair enough.
Have you ever seen classes where the default constructor and destructor are explicitly defaulted? Like this?
class SomeClass {
public:
SomeClass() = default;
~SomeClass() = default;
void foo();
private:
int m_num{42};
};
]]>First of all, that’s not the best idea. You can simply remove them. But let’s assume that you cannot remove the user-provided destructor for some reason. Maybe it’s not defaulted and it does something.
What does the famous Hinnant table tell us?
Your attention is invited to the fourth part of an e-book on undefined behavior. This is not a textbook, as it's intended for those who are already familiar with C++ programming. It's a kind of C++ programmer's guide to undefined behavior and to its most secret and exotic corners. The book was written by Dmitry Sviridkin and edited by Andrey Karpov.
C++ programmer's guide to undefined behavior: part 4 of 11
by Dmitry Sviridkin
From the article:
]]>In the C++98, the committee made a terrible decision that seemed reasonable at the time. They created a specialization for std::vector<bool>. Normally, sizeof(bool) == sizeof(char), but one bit is enough for bool. However, 99.99% of all possible platforms can't address memory one bit at a time. Let's pack bits in vector<bool> and store CHAR_BIT (usually 8) boolean values in one byte (char) for more efficient memory utilization. As a result, one needs to work with std::vector<bool> in a very special way...
Before C++20, constant evaluation couldn't handle allocations, causing any such attempts to fail. This changed with C++20, which introduced the ability to allocate memory during constant evaluation, although with strict limitations requiring deallocation during the same evaluation period. Despite these advancements, certain operations, such as declaring a constexpr std::vector
, remain impossible, with the goal of this blog post being to explore why these limitations persist.
What's so hard about
constexpr
allocation?by Barry Revzin
From the article:
]]>Before C++20, we couldn’t have any allocation during constant evaluation at all. Any attempt to do so would fail the evaluation — it would no longer be constant.
In C++20, as a result of P0784R7, that changed. Finally we could do allocation during constant evaluation. However, this evaluation was extremely limited. Specifically, any allocation that happens must be deallocated during that constant evaluation.
This opened the door to a wide range of operations that weren’t possible before. We can now have local
std::string
s andstd::vector<T>
s! Those just work!But we still cannot just declare a
constexpr std::vector
:#include <vector> constexpr std::vector<int> v = {1, 2, 3}; // error
And we cannot even declare a local
constexpr std::vector
in aconsteval
function:#include <vector> consteval auto f() -> int { constexpr std::vector<int> v = {4, 5, 6}; // still error return v.size(); }
This limitation still remains in C++23 and could very well still remain in C++26. The goal of this blog post is to explain why we haven’t just solved this problem already.
Do we need a default constructor? What does it mean to have a default constructor? What happens if we don’t have one? Those are the questions we are going after in this article.
What to do if you don't want a default constructor?
by Sandor Dargo
From the article:
]]>A default constructor is a constructor that takes no arguments and initializes - hopefully - all the members with some default values. If you define no constructors at all, it’ll even be generated for you.
Do we need default constructors?
It really depends. Let’s first approach this question from a design point of view. Does it make sense to represent an object where the members are default initialized?
If you represent a tachograph, it probably makes sense to have such a default state where all the counters are initialized to zero.
The tachograph is the device that records driving times and rest periods as well as periods of other work and availability taken by the driver of a heavy vehicle.
On the other hand, if you represent a person or a task identifier - which inspired me to write this article - it doesn’t. A person with an empty name or a task ID with an empty ID doesn’t make much sense.
Well, that’s the case from a design point of view. What about the technical aspects? What happens if we don’t have a default constructor?
Working with the filesystem can be a daunting task, but it doesn’t have to be. In this post, I’ll walk you through some of the most common filesystem operations using the powerful features introduced in C++17, as well as some new enhancements in C++20/23. Whether you’re creating directories, copying files, or managing permissions, these examples will help you understand and efficiently utilize the std::filesystem
library.
22 Common Filesystem Tasks in C++20
by Bartlomiej Filipek
From the article:
]]>Creating directories is a basic yet essential operation. The
std::filesystem
library makes this straightforward with thecreate_directory
function.
Your attention is invited to the third part of an e-book on undefined behavior. This is not a textbook, as it's intended for those who are already familiar with C++ programming. It's a kind of C++ programmer's guide to undefined behavior and to its most secret and exotic corners. The book was written by Dmitry Sviridkin and edited by Andrey Karpov.
C++ programmer's guide to undefined behavior: part 3 of 11
by Dmitry Sviridkin
From the article:
]]>This program, built by GCC 10.1, -std=c++20 -O3, doesn't crash, but it doesn't output anything either. If we take GCC 14.1 and the same keys, we suddenly get "helloworld" in the output. It's old but gold undefined behavior.
I had the privilege to attend and present at C++ on Sea 2024 for the 5th time in a row!
Trip Report: C++ On Sea 2024
by Sandor Dargo
From the article:
]]>The conference had a very strong start. Right after the keynote by Dave Abrahams, 4 incredible speakers were on stage at the same time on the 4 different tracks. Jason Turner, Walter E Brown, Nico Josuttis, Mateusz Pusz…
If a conference could have these people throughout the whole program, it would already be a strong conference. C++ On Sea proposed such a strong line-up that these people could be scheduled at the same time. It didn’t make my decision easier, so I chose based on the topic, and I wanted to grow my knowledge on
constexpr
so I stayed in themain()
room.
C++17 introduced std::monostate
, a dummy type with no members and trivial functions, primarily used when no action is needed. Despite its simplicity, std::monostate
plays a crucial role in scenarios like coroutines and std::variant
, where a default-constructible placeholder type is required.
What’s the point of std::monostate? You can’t do anything with it!
by Raymond Chen
From the article:
]]>C++17 introduced
std::
, and I used it as a placeholder to represent the results of a coroutine that produces nothing. In the comments, Neil Rashbrook asked what you are expected to do with amonostate std::
, seeing as has no members and only trivial member functions.monostate The answer is “nothing”.
The purpose of
std::
is to be a dummy type that does nothing. All instances are considered equal to each other. It is basically this:monostate struct monostate {}; // plus relational operators and a hash specializationYou can see it in libcxx (LLVM/clang), libstdc++ (gcc), and stl (msvc).
Concurrency is a complicated topic. Lucian Radu Teodorescu provides a simple theory of concurrency which is easy to reason about and apply.
Concurrency: From Theory to Practice
by Lucian Radu Teodorescu
From the article:
]]>One of the big challenges with concurrency is the misalignment between theory and practice. This includes the goals of concurrency (e.g., improving the performance of the application) and the means we use to achieve that goal (e.g., blocking primitives that slow down the program). The theory of concurrency is simple and elegant. In practice, concurrency is often messy and strays from the good practices of enabling local reasoning and using structured programming.
We present a concurrency model that starts from the theory of concurrency, enables local reasoning, and adheres to the ideas of structured programming. We show that the model can be put into practice and that it yields good results.
Most of the ideas presented here are implemented in a C++ library called concore2full [concore2full]. The library is still a work in progress. The original goal for this model and for this library was its inclusion in the Hylo programming language [Hylo]. For Hylo, we want a concurrency model that allows local reasoning and adheres to the structured programming paradigm. We also wanted a model in which there is no function colouring [Nystrom15], in which concurrency doesn’t require a different programming paradigm.
This article is based on a talk I gave at the ACCU 2024 conference [Teodorescu24]. The conference was great! The programme selection was great; there was always something of interest to me. With many passionate C++ engineers and speakers, the exchange of information between participants was excellent; as they say, the best track was the hallway track. I highly encourage all C++ enthusiasts (and not just C++) to participate in future ACCU conferences.
How do you expose a C++ object to a TypeScript layer or other scripting language? Russell K. Standish demonstrates an approach using a RESTService API that is scripting-language independent.
Fat API Bindings of C++ Objects into Scripting Languages
by Russell K. Standish
From the article:
]]>A fat API exposes nearly all of a C++ object’s public attributes and methods to a consuming environment, such as a scripting language, or web client. This can be contrasted with a conventional, or thin API, where the API is defined up front, and the C++ object provides the implementation, most of which is private to the C++ layer.
Obviously, reflection is required to expose C++ objects to a consuming layer like this – this paper explores using the Classdesc system to implement reflection of C++ objects into a JavaScript/TypeScript environment via a REST service, and also via a Node.js API module.