Articles & Books

VS Code with MS C/C++ Extension: A Confusing UI Design Choice -- Giovanni Dicanio

I wanted to use VS Code with the MS C/C++ Extension for some C++ teaching. In the process, I discovered that setting the C++ language standard to build your C++ code is not as easy and intuitive as one would expect. I discussed the details in this article:

VS Code with MS C/C++ Extension: A Confusing UI Design Choice

by Giovanni Dicanio

From the article:

I pressed Ctrl+Shift+P, selected C/C++ Edit Configurations (UI), and in the C/C++ Configurations page, selected c++20 for the C++ standard.

Then I pressed F5 to start a debugging session, preceded by a build process, and saw that the build process failed.

I took a look at the error message in the terminal window, and to my surprise the error messages were telling me that some libraries (like <span>) were available only with C++20 or later. But I had just selected the C++20 standard a few minutes ago!

 

Inside STL: The atomic shared_ptr -- Raymond Chen

RaymondChen_5in-150x150.jpgThe C++20 standard introduced a specialization of std::atomic for shared pointers: std::atomic<shared_ptr<T>>. How does it work?

Inside STL: The atomic shared_ptr

by Raymond Chen

From the article:

Recall that a normal shared_ptr consists of two pointers: A stored pointer that the shared_ptr returns when you call get() and a pointer to a control block which holds the strong reference count, the weak reference count, and a pointer to the managed object.

The atomic version of the shared_ptr has the same layout, with one change: The bottom two bits of the pointer to the control block are used as flags.

Exercise: Why use the control block pointer instead of the stored pointer to store the flags?

Both the glibc++ libstdc++ and msvc implementations use the bottom bit of the control block pointer as a lock bit: Before performing an operation on the atomic shared pointer, the implementation atomically sets the lock bit to indicate that an atomic operation is in progress. If anybody tries to set the lock bit and finds that it’s already set, they wait for bit to clear. When the owner of the lock bit completes the atomic operation, it clears the lock bit, allowing any waiting threads to proceed.

The difference between libstdc++ and msvc is how they wait for the lock bit to clear.

How frivolous use of polymorphic allocators can imbitter your life

Do you want to stay ahead of the curve and start to use polymorphic allocators in your projects? Are you undeterred even by the cost of virtual calls? If so, it's time to talk about the nuances with a lifetime and why you can't just replace containers with analogs from the 'pmr' namespace.

How frivolous use of polymorphic allocators can imbitter your life

by Grigory Semenchev

From the article:

The developers' prayers have been answered! Starting with C++17, you can use polymorphic allocators. Their idea is a little different from what we need. What is important is that they allow you to specify a buffer from which the allocator will take memory. Such a buffer is called a resource, and it must be inherited from std::memory_resource. It's also important that C++17 introduced several standard resources. Let's examine std::unsynchronized_pool_resource as an example. I should admit that all options mentioned in this article mostly apply to all standard library resources.

C++26: Delete with a Reason -- Sandor Dargo

SANDOR_DARGO_ROUND.JPGLet’s start exploring C++26 with a simple but useful change. Thanks to Yihe Li’s proposal (2573R2), when we =delete a special member function or a function overload, we can specify a reason.

C++26: Delete with a Reason

by Sandor Dargo

From the article:

This is a practical and not very surprising change. Why do I say it’s not very surprising? Adding the ability to specify reasons for some operations has become a pattern in C++ evolution. Since C++11, we can add an optional message to static_assert, C++14 enriched the [[deprecated]] attribute with an optional reason, and the same happened in C++20 to the [[nodiscard]] attribute.

The reason is always similar, if not the same, to enhance maintainability and readability. When it comes to readability, not only the code but also the diagnostic messages become easier to grasp. Understanding why the compiler emitted a warning or an error becomes easier.

This latter is even more important than the readability of the codebase. I mean you could still add a comment right before or after a deleted function, but it wouldn’t be used in the compilers’ messages.

The usage is simple, if you want to specify a reason why a function or function overload is deleted, just pass a string literal containing the reason after delete surrounded by parentheses.

class NonCopyable
{
public:
    // ...
    NonCopyable() = default;

    // copy members
    NonCopyable(const NonCopyable&)
        = delete("Since this class manages unique resources, copy is not supported; use move instead.");
    NonCopyable& operator=(const NonCopyable&)
        = delete("Since this class manages unique resources, copy is not supported; use move instead.");
    
    // provide move members instead
    // ... 
};

When a default-initializable type actually isn't -- Kaashif Hymabaccus

Depositphotos_170038592_S.jpgThe C++ proposal for indirect and polymorphic introduces two new class templates designed to simplify working with dynamically allocated types while retaining value semantics. This post dives into a curious case with polymorphic, exploring why std::default_initializable<polymorphic<T>> evaluates to true—even when polymorphic<T> can't actually be default-initialized in practice.

When a default-initializable type actually isn't

by Kaashif Hymabaccus

From the article:

I was looking at a proposal for adding value-semantic dynamically-allocated types to C++. You can find it here. This proposal adds two class templates, indirect and polymorphic.

The primary use case for this is to make it easier to write correct composite classes. A motivating example can be found through existential types.

Suppose you want a type T which owns (ownership is key) some objects implementing some interfaces. You don't know what concrete types the sub-objects have, and you don't care. You're fine with dynamic dispatch, with the overhead that entails at runtime. You want value semantics, meaning (among other things) copying T copies the sub-objects too.

In Rust, this is expressible through Box<dyn T>, which lets us own objects and perform dynamic dispatch with value semantics. This is what polymorphic will allow in C++, allowing you to avoid hacking around unique_ptr. See the proposal above for more detailed motivation.

 

Senders/Receivers: An Introduction -- Lucian Radu Teodorescu

Depositphotos_498706732_S.jpgC++26 will introduce a new concurrency feature called std::execution, or senders/receivers. Lucian Radu Teodorescu explains the idea and how to use these in detail.

Senders/Receivers: An Introduction

by Lucian Radu Teodorescu

From the article:

In June 2024, at the WG21 plenary held in St. Louis, the P2300R10: std::execution paper [P2300R10], also known as senders/receivers, was formally adopted for inclusion in C++ 26. The content of the paper quickly found its way into the working draft for the C++ standard [WG21]. You can find more about the highlights of the St. Louis meeting in Herb Sutter’s trip report [Sutter24].

Senders/receivers represent one of the major additions to C++, as they provide an underlying model for expressing computations, adding support for concurrency, parallelism, and asynchrony. By using senders/receivers, one can write programs that heavily and efficiently exploit concurrency, all while maintaining thread safety (no deadlocks, race conditions, etc.). This is applicable not only to a few classes of concurrent problems but, at least in theory, to all types of concurrency problems. Senders/receivers provide a cost-free way of expressing computations that can run on different hardware with different constraints. They support creating computation chains that execute work on the CPU, GPU, and also enable non-blocking I/O.

Although the proposal has many advantages, there are still people who see the addition of this feature to the C++ standard at this point as a mistake. Some of the cited reasons are the complexity of the feature, compilation times, immaturity, and teachability. The last one caught my attention.

In this article, I plan to provide an introduction to senders/receivers as described in P2300 (and some related papers). The goal is not necessarily to showcase the many advantages of this model or delve into the details of complex topics. Rather, it is to offer a gentle introduction for those who have never read the paper or watched a talk on senders/receivers. We want the reader to understand the basic concepts of using senders/receivers without needing to grasp the intricate details of their implementation.

The hope is that, by the end of the article, the reader will be able to write some programs that use senders/receivers. The examples here are written as if the reader is coding with the feature already included in the standard library. Currently, no standard library provider ships senders/receivers; however, the reader can use the reference implementation of the feature [stdexec].

Static Reflection in C++ -- Wu Yongwei

Depositphotos_226578348_S.jpgStatic reflection is under consideration for C++26. Wu Yongwei demonstrates how to achieve reflection now and shows some examples of what C++26 might make possible.

Static reflection will be an important part of C++ compile-time programming, as I discussed in the October issue of Overload [Wu24]. This time I will discuss static reflection in detail, including how to emulate it right now, before it’s been added to the standard.

Static Reflection in C++

by Wu Yongwei

From the article:

Background

Many programming languages support reflection (Python and Java, for example). C++ is lagging behind.

While this is the case, things are probably going to change in C++26. Also, what will be available in C++ will be very different from what is available in languages like Java or Python. The keyword is ‘static’.

Andrew Sutton defined ‘static reflection’ as follows [Sutton21]:

Static reflection is the integral ability for a metaprogram to observe its own code and, to a limited extent, generate new code at compile time.

‘Compile-time’ is the special sauce in C++, and it allows us to do things impossible in other languages:

  • Zero-overhead abstraction. As Bjarne Stroustrup famously put it, ‘What you don’t use, you don’t pay for. What you do use, you couldn’t hand-code any better.’ If you do not need static reflection, it will not make your program fatter or slower. But it will be at your hand when you do need it.
  • High performance. Due to the nature of compile-time reflection, it is possible to achieve unparalleled performance, when compared with languages like Java or Python.
  • Versatility at both compile time and run time. The information available at compile time can be used at run time, but not vice versa. C++ static reflection can do things that are possible in languages like Java, but there are things that C++ can do but are simply impossible in other languages.

What we want from reflection

When we talk about static reflection, what do we really want? We really want to see what a compiler can see, and we want to be able to use the relevant information in the code. The most prominent cases are enum and struct. We want to be able to iterate over all the enumerators, and know their names and values. We want to be able to iterate over all the data members of a struct, and know their names and types. Obviously, when a data member is an aggregate, we also want to be able to recurse into it during reflection. And so on.

Regretfully, we cannot do all these things today with ‘standard’ definitions. Yes, in some implementations it is possible to hack out some of the information with various tricks. I would prefer to use macros and template techniques to achieve the same purpose, as the code is somewhat neater, more portable, and more maintainable – at the cost of using non-standard definition syntaxes. Of course, nothing beats direct support from the future C++ standard.

C++ programmer's guide to undefined behavior: part 12 of 11

Your attention is invited to the final 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. Why is it the chapter 12 of 11? We couldn't resist highlighting the favorite error of C and C++ programmers that even has its own name—the Off-by-one Error.

C++ programmer's guide to undefined behavior: part 12 of 11

by Dmitry Sviridkin

From the article:

LLVM can generate the ud2 instruction on x86, which is an invalid instruction, often used as an indicator of unreachable code. If the program tries to execute it, it'll crash with the SIGILL signal. The code that causes undefined behavior can be marked as unreachable and replaced with ud2 or discarded in the future. In our wonderful example, the compiler is fully aware that buffer.size() == 0, and it hasn't been changed.

 

Learning to read C++ Compiler Errors: Failing to Create a shared_ptr -- Raymond Chen

Depositphotos_478507824_S.jpgThe trick to understanding C++ compiler error messages is to focus on two things. First, look at the beginning of the error message, which tells you what went wrong at a very low level. Then skip over the intermediate errors that follow the chain of calls until you end up at the line of code that you wrote. That original line of code is the one that is leading the compiler to a bad place. After that, you sometimes get supplemental information that helps you understand the low-level error better.

Learning to read C++ Compiler Errors: Failing to Create a shared_ptr

by Raymond Chen

From the article:

Consider the following erroneous code:

#include <memory>
#include <string>

struct WidgetOptions
{
    // imagine there is interesting stuff here
};

struct Widget
{
    Widget(WidgetOptions const* options);

    // imagine there is other interesting stuff here
};

void oops()
{
    WidgetOptions options;

    // The next line fails to compile
    std::shared_ptr<Widget> widget =
        std::make_shared<Widget>(options);
}

Here comes the error explosion.

// gcc
In file included from bits/stl_tempbuf.h:61,
                 from memory:66,
                 from sample.cpp:1:
bits/stl_construct.h: In instantiation of 'void std::_Construct(_Tp*, _Args&& ...) [with _Tp = Widget; _Args = {WidgetOptions&}]':
bits/alloc_traits.h:657:19:   required from 'static void std::allocator_traits<std::allocator<void> >::construct(allocator_type&, _Up*, _Args&& ...) [with _Up = Widget; _Args = {WidgetOptions&}; allocator_type = std::allocator<void>]'
  657 |         { std::_Construct(__p, std::forward<_Args>(__args)...); }
      |           ~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bits/shared_ptr_base.h:607:39:   required from 'std::_Sp_counted_ptr_inplace<_Tp, _Alloc, _Lp>::_Sp_counted_ptr_inplace(_Alloc, _Args&& ...) [with _Args = {WidgetOptions&}; _Tp = Widget; _Alloc = std::allocator<void>; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]'
  607 |           allocator_traits<_Alloc>::construct(__a, _M_ptr(),
      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~
  608 |               std::forward<_Args>(__args)...); // might throw
      |               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
bits/shared_ptr_base.h:969:16:   required from 'std::__shared_count<_Lp>::__shared_count(_Tp*&, std::_Sp_alloc_shared_tag<_Alloc>, _Args&& ...) [with _Tp = Widget; _Alloc = std::allocator<void>; _Args = {WidgetOptions&}; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]'
  969 |           auto __pi = ::new (__mem)
      |                       ^~~~~~~~~~~~~
  970 |             _Sp_cp_type(__a._M_a, std::forward<_Args>(__args)...);
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bits/shared_ptr_base.h:1713:14:   required from 'std::__shared_ptr<_Tp, _Lp>::__shared_ptr(std::_Sp_alloc_shared_tag<_Tp>, _Args&& ...) [with _Alloc = std::allocator<void>; _Args = {WidgetOptions&}; _Tp = Widget; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]'
 1713 |         : _M_ptr(), _M_refcount(_M_ptr, __tag, std::forward<_Args>(__args)...)
      |                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bits/shared_ptr.h:463:59:   required from 'std::shared_ptr<_Tp>::shared_ptr(std::_Sp_alloc_shared_tag<_Tp>, _Args&& ...) [with _Alloc = std::allocator<void>; _Args = {WidgetOptions&}; _Tp = Widget]'
  463 |         : __shared_ptr<_Tp>(__tag, std::forward<_Args>(__args)...)
      |                                                                  ^
bits/shared_ptr.h:1007:14:   required from 'std::shared_ptr<typename std::enable_if<(! std::is_array<_Tp>::value), _Tp>::type> std::make_shared(_Args&& ...) [with _Tp = Widget; _Args = {WidgetOptions&}; typename enable_if<(! is_array<_Tp>::value), _Tp>::type = Widget]'
 1007 |       return shared_ptr<_Tp>(_Sp_alloc_shared_tag<_Alloc>{__a},
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1008 |                              std::forward<_Args>(__args)...);
      |                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sample.cpp:22:33:   required from here
   22 |         std::make_shared<Widget>(options);
      |         ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
bits/stl_construct.h:119:7: error: no matching function for call to 'Widget::Widget(WidgetOptions&)'
  119 |       ::new((void*)__p) _Tp(std::forward<_Args>(__args)...);
      |       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sample.cpp:11:5: note: candidate: 'Widget::Widget(const WidgetOptions*)'
   11 |     Widget(WidgetOptions const* options);
      |     ^~~~~~
sample.cpp:11:33: note:   no known conversion for argument 1 from 'WidgetOptions' to 'const WidgetOptions*'
   11 |     Widget(WidgetOptions const* options);
      |            ~~~~~~~~~~~~~~~~~~~~~^~~~~~~
sample.cpp:9:8: note: candidate: 'constexpr Widget::Widget(const Widget&)'
    9 | struct Widget
      |        ^~~~~~
sample.cpp:9:8: note:   no known conversion for argument 1 from 'WidgetOptions' to 'const Widget&'
sample.cpp:9:8: note: candidate: 'constexpr Widget::Widget(Widget&&)'
sample.cpp:9:8: note:   no known conversion for argument 1 from 'WidgetOptions' to 'Widget&&'
Compiler returned: 1
 

Replace CRTP with Concepts -- Sandor Dargo

SANDOR_DARGO_ROUND.JPGThis article explores how C++20 concepts can replace CRTP when implementing static interfaces for a family of classes. By leveraging concepts, we achieve cleaner, more readable, and less error-prone code—provided you have access to C++20.

Replace CRTP with Concepts

by Sandor Dargo

From the article:

In my Meeting C++ 2024 trip report, among my favourite ideas I mentioned Klaus Iglberger’s talk where he mentioned the possibility of replacing the curiously returning template pattern with the help of class tagging and concepts.

Class tagging might mean different things in different contexts, or at least might be implemented in different ways. The end goal is to mark, in other words, tag classes or functions to be used in certain contexts, with certain algorithms. As you’ll see, in our case it’ll also be a tool to prevent duck typing.

We are going to see an example implementation of a static interface with CRTP with a couple of different derived classes, then we’ll see the implementation without CRTP.

The CRTP solution

With the static interface, we are creating a static family of types. There is no need for dynamic polymorphism to share the same interface. It’s still granted through a base class, which is a template taking the deriving class as a parameter.