Articles & Books

Bjarne Stroustrup's Vision for 21st Century C++ -- Darryl K. Taft & David Cassel

stroustrupvision.pngBjarne Stroustrup, the creator of C++, has outlined his vision for the language’s future in his article "21st Century C++," emphasizing the need for safer and more modern coding practices without abandoning its powerful legacy. His approach advocates for incremental improvements, such as guideline-enforcing profiles and enhanced type safety, ensuring C++ remains relevant in an era of heightened security and performance demands.

Bjarne Stroustrup's Vision for 21st Century C++: Balancing Legacy, Safety, and Innovation

by Darryl K. Taft & David Cassel

From the article:

Bjarne Stroustrup, the mind behind the venerable C++ programming language, has once again entered the fray to shape the language’s future, publishing a weighty, 6,300-word piece titled "21st Century C++" in Communications of the ACM. Central to his argument is the idea that while C++ itself cannot drastically change due to its extensive legacy, the practices surrounding it can and must adapt. His manifesto outlines a path forward, advocating for tools like guideline-enforcing profiles to detect and mitigate coding errors, alongside a deliberate, type-safe evolution of the language. Stroustrup's call to action is as much about preserving the language's relevance in an era of heightened attention to memory safety as it is about addressing criticism aimed at C++’s historical complexities.

"21st Century C++" and the Pursuit of Safety Without Sacrificing Power

C++ is a linguistic titan. It powers systems that range from high-frequency trading platforms to embedded systems in spacecraft, and yet its intricate, low-level control has often been a double-edged sword. For Stroustrup, the path forward lies in preserving this power while ensuring safer usage—a delicate dance that avoids alienating the vast community reliant on its compatibility with decades-old code. In "21st Century C++," Stroustrup suggests that the way forward lies in shifting the culture and tools surrounding the language, rather than the language itself.

One of the most critical elements of his vision is the concept of "guideline-enforcing profiles."

The Case of the Crash When Trying to Erase an Element from a std::set -- Raymond Chen

RaymondChen_5in-150x150.jpgToday, we’ll look at a crash that occurred when trying to erase an element from a std::set.

The Case of the Crash When Trying to Erase an Element from a std::set

by Raymond Chen

From the article:

rax=000001f565bc046e rbx=000001f589b20340 rcx=000001f565bc046e
rdx=000000e6658feca8 rsi=000001f589b20690 rdi=000001f589b203c0
rip=00007ffdd4726bc4 rsp=000000e6658fec30 rbp=0000388a1713ab55
 r8=000001f589b895d0  r9=000001f589b895d0 r10=000001f589000140
r11=0000000000000000 r12=0000000000000001 r13=000000007ffe0385
r14=0000000000000000 r15=000001f589b8f900

LitWare!std::_Tree<std::_Tset_traits<WidgetWatcher *,
        std::less<WidgetWatcher *>,
        std::allocator<WidgetWatcher *>,0> >::_Eqrange+0x14
    [inlined in LitWare!std::_Tree<std::_Tset_traits<
        WidgetWatcher *,std::less<WidgetWatcher *>,
        std::allocator<WidgetWatcher *>,0> >::erase+0x18]:
00007ffd`d4726bc4 cmp     byte ptr [rax+19h],r11b ds:000001f5`65bc0487=??

The stack trace has some information about how we got here.

LitWare!std::_Tree<std::_Tset_traits<Widget *,
        std::less<Widget *>,
        std::allocator<Widget *>,0> >::_Eqrange+0x14
LitWare!std::_Tree<std::_Tset_traits<Widget *,
        std::less<Widget *>,
        std::allocator<Widget *>,0> >::erase+0x18
LitWare!Widget::~Widget+0xc8
LitWare!Widget::`scalar deleting destructor'+0x14
LitWare!DestroyWidget+0x15
Fabrikam!Doodad::~Doodad+0x75
Fabrikam!Doodad::`scalar deleting destructor'+0x14
Fabrikam!Doodad::Release+0x40
Contoso!Gadget::~Gadget+0x66
ucrtbase!<lambda_⟦...⟧>::operator()+0xa5
ucrtbase!__crt_seh_guarded_call<int>::operator()<⟦...⟧>+0x3b
ucrtbase!__acrt_lock_and_call+0x1c
ucrtbase!_execute_onexit_table+0x3d
Contoso!dllmain_crt_process_detach+0x45
Contoso!dllmain_dispatch+0xe6
ntdll!LdrpCallInitRoutine+0xb0
ntdll!LdrShutdownProcess+0x260
ntdll!RtlExitUserProcess+0x114
kernel32!FatalExit+0xb
ucrtbased!exit_or_terminate_process+0x3a
ucrtbased!common_exit+0x85
ucrtbased!exit+0x16

C++ for Embedded Systems: constexpr and consteval -- Andreas Fertig

me.pngIn today's post, I'll learn how modern C++ can influence the code you write for your embedded system. You will see code using up to C++23. The example I show you below circles around at least two questions I got various times from customers: What is consteval good for? What is that user-defined literal operator, and why should I care?

C++ for Embedded Systems: constexpr and consteval

by Andreas Fertig

From the article:

Chapter One: What is a MAC address?

I teach a lot of classes to customers who are developing embedded systems. That makes sense. I worked for a long time in that domain, and I enjoyed it so much.

One recurring topic is networking. While nowadays we have various different network types and technologies, let's use the Internet Protocol (IP) today. The base of network communication is your Network Interface Card (NIC). Each NIC has a unique Medium Control Address (MAC) assigned. The MAC address is the base layer for everything on top, like TCP/IP.

A MAC address consists of exactly six bytes. One way to represent a MAC address in code is this:

embedded-fertig.png

A Simplified Overview of Ways to Add or Update Elements in a std::map -- Raymond Chen

RaymondChen_5in-150x150.jpgThe std::map subscript operator ([]) is a convenient but sometimes dangerous feature, as it can create unintended default-constructed entries. By understanding the behavior of various map insertion and lookup methods—such as insert, emplace, try_emplace, and insert_or_assign—developers can write more efficient and predictable code while avoiding unnecessary key-value creations and duplicate lookups.

A Simplified Overview of Ways to Add or Update Elements in a std::map

by Raymond Chen

From the article:

Some time ago, I mentioned how the std::map subscript operator is a dangerous convenience. In that article, I linked to an overview of the insertion emplacement methods, but I’m going to recapture the essential points in a table.¹

In the table below, the discussion of “consumed” or “not consumed” refers to the case that v is an rvalue reference like std::move(something).

stdmap-chen.png

We can reorganize the table by effect.
stdmap2-chen.png
Exercise: Why are the bottom left two boxes blank?

C++26: pack indexing -- Sandor Dargo

SANDOR_DARGO_ROUND.JPGC++26 introduces pack indexing as a core language feature, making it significantly easier to extract specific elements from parameter packs using a familiar subscript syntax. This improvement, proposed by Corentin Jabot and Pablo Halpern, eliminates the need for cumbersome workarounds like recursive templates or boolean expression tricks, providing a more intuitive and readable approach.

C++26: pack indexing

by Sandor Dargo

From the article:

C++11 introduced parameter packs to provide a safer way to pass an undefined number of parameters to functions instead of relying on variadic functions.

While packs are a useful feature, and since C++17 it’s so easy to use them in fold expressions, extracting a specific element of a pack is somewhat cumbersome.

You either have to rely on some standard functions not made for the purpose or use “awkward boolean expression crafting or recursive templates”. None of them is unbearable, but it might be error-prone or simply expensive regarding compile-time performance. Nevertheless, they are not the most readable solutions.

C++26 brings us pack indexing as a core language feature thanks to the proposal of Corentin Jabot and Pablo Halpern, P2662R3.

Before discussing some interesting points, first, let’s look at an example:

packindexing-dargo.png

Improving Code Safety in C++26: Managers and Dangling References -- Bartlomiej Filipek

codesafety2-filipek.pngIn this blog post, we’ll explore ways to improve the safety of a simple configuration manager. We’ll handle common pitfalls like dangling references and excessive stack usage. Additionally, we’ll see how C++26 helps enforce safer coding practices with stricter diagnostics and improved handling of large objects.

Improving Code Safety in C++26: Managers and Dangling References

by Bartlomiej Filipek

From the article:

Step 1: The Buggy Implementation 
 
Below is a simple example of a manager object that stores various configs in a map and provides a method to retrieve them. When a requested configuration isn’t found, the code attempts to return a default certificate:

codesafety-filipek.png

Do you see a potential error in this code?

. . .

At first glance, the code looks harmless. However, if the requested entry isn’t found, the function returns a reference to a temporary std::vector. Once the function exits, that temporary is destroyed—leaving you with a dangling reference and undefined behavior.

Inside STL: Waiting for a std::atomic to change, part 2 -- Raymond Chen

RaymondChen_5in-150x150.jpgLast time, we looked at how the Microsoft C++ standard library implements wait and notify_* for std::atomic<std::shared_ptr<T>>. Today, we’ll look at the other library that (as of this writing) implements std::atomic<std::shared_ptr<T>>: libstdc++.

Inside STL: Waiting for a std::atomic<std::shared_ptr<T>> to change, part 2

by Raymond Chen

From the article:

The first thing to note is that the traditional “wait for a value to change” mechanism on unix is the futex, but futexes (futexen?) are limited to 4-byte values, which is insufficient for a 64-bit pointer, much less the two pointers inside a shared_ptr.

At this point, I will refer you to learn about how libstdc++ implements waits on atomic values, particularly the section on how it handles types that do not fit in a __platform_wait_t. The remainder of this discussion will treat that as an already-solved problem and focus on the shared pointer part.

Okay, back to atomic<shared_ptr<T>>::wait():

// atomic<shared_ptr<T>>::wait
void
wait(value_type __old,
     memory_order __o = memory_order_seq_cst) const noexcept
{
    _M_impl.wait(std::move(__old), __o);
}

When you wait on a shared_ptr, the work is done by _Sp_atomic::wait:

// _Sp_atomic<shared_ptr<T>>::wait
void
wait(value_type __old, memory_order __o) const noexcept
{
    auto __pi = _M_refcount.lock(memory_order_acquire);
    if (_M_ptr == __old._M_ptr && __pi == __old._M_refcount._M_pi)
      _M_refcount._M_wait_unlock(__o);
    else
      _M_refcount.unlock(memory_order_relaxed);
}

Making C++ Safer -- Greg Law

Should you start new projects in C++, these days? Is language safety an issue for you? What can be done, today?

Making C++ Safer

by Greg Law

From the article:

I believe that over time C++ will become a lot safer, maybe even some kind of ‘safe’. Competition is good: Clang was the best thing to happen to GCC, and Rust might turn out to be the best thing to happen to C++. That journey has already begun, with proposals for the evolution of the language including Contracts and Profiles, and simply changing some of the defaults in C++26. While the language custodians work to make the language itself safer, what can you do today?

 

Inside STL: Waiting for a std::atomic to change, part 1 -- Raymond Chen

RaymondChen_5in-150x150.jpgWhen using std::atomic<std::shared_ptr<T>>, the C++ standard defines a "change" as a modification to either the stored pointer or the control block pointer. However, since atomic wait mechanisms typically track only a single memory address, the Microsoft implementation handles this limitation by using a timeout-based polling strategy to detect changes in the control block.

Inside STL: Waiting for a std::atomic<std::shared_ptr<T>> to change, part 1

by Raymond Chen

From the article:

Like other std::atomic specializations, std::atomic<std::shared_ptr<T>> supports the wait and notify_* methods for waiting for the value to change and reporting that the value has changed. The definition of “changed” in the C++ language specification is that the value has changed if either the stored pointer or the control block pointer has changed. A shared pointer is implemented as a pair of pointers, but Wait­On­Address can wait on at most 8 bytes, and unix futexes can wait on only four bytes, so how does this work?¹

The Microsoft implementation waits for the stored pointer to change, and the notify_* methods signal the stored pointer. But wait, this fails to detect the case where the stored pointer stays the same and only the control block changes.

std::atomic<std::shared_ptr<int>> p =
    std::make_shared<int>(42);

void change_control_block()
{
    auto old = p.load();
    auto empty = std::shared_ptr<int>();

    // Replace with an indulgent shared pointer
    // with the same stored pointer.
    p.store({ empty, old.get() });
    p.notify_all();
}

void wait_for_change()
{
    auto old = p.load();
    p.wait(old);
}

C++26: A Placeholder with No Name -- Sandor Dargo

SANDOR_DARGO_ROUND.JPGIn this post, we are going to discuss a core language feature proposed by Corentin Jabot and Micheal Park in P2169R4. With the new standard we get a cool unnamed placeholder.

C++26: A Placeholder with No Name

by Sandor Dargo

From the article:

By convention, when we have a variable whose value we don’t want to use or care about, we often name it _. The problem is that with higher warning levels (-Wunused-variable), our compilation might fail because _ is unused.
int foo() {
return 42;
}

auto _ = foo();
/* error: unused variable '_' [-Werror,-Wunused-variable] */

To avoid this problem, we must mark it [[maybe_unused]].