Risk Industry Role Insights AI/ML Home

CPP Con Recap

Industry

by Nick

CppCon 2026 Recap

Susquehanna engineers recently attended CppCon 2026 in Aurora, Colorado to meet with the C++ community and to learn about the latest developments in C++. Most talks centered around new features arriving in C++26 and C++29, most notably reflection and contracts.

Susquehanna engineers attended dozens of talks. Here are some of our favorites. 

 

Profiles for Simplicity and Guarantees, Bjarne Stroustrup 

The conference opened with a talk given by C++ creator, and Susquehanna’s own, Bjarne Stroustrup. He discussed profiles, which are a mechanism for providing a set of safety guarantees for code. It does this by using a ‘subset of a superset’ strategy where the language is supersetted with libraries and profiles provide a mechanism to get a subset of behavior where the undesirable behavior is removed. 

The goal for this feature is to come to parity with other memory-safe languages such as Rust and C# while still allowing for the more direct control that C++ enables. While you can currently write C++ code that does not have memory-related errors, you’d have to rely on a slew of non-standardized 3rd party tools to check it. You can also use modern C++ features such as smart pointers to avoid certain memory errors, but you don’t have a way to ensure libraries your code depends on do the same. With profiles, you can apply specific profiles to your code and require that libraries you consume do the same. 

In his presentation, Stroustrup presented three profiles to be included in the standard: initialization, invalidation, and ranges.

 

Initialization Profile 

The initialization profile ensures that an object is initialized before it is used, is only initialized and destroyed once, and is destroyed before its memory is reused. It provides these guarantees by checking where an object is initialized and requiring that the object be initialized before it is used. This feature also requires that an object be initialized when declared unless explicitly marked otherwise, to force developers to be specific about their intent. 

For the below examples, this fork of gcc was used. 

// enable initialization profile
[[profiles::enforce(std::init)]]; 

void square(int) {
    class MyObj { public: uint32_t myVal; };
    class MyInitedObj { public: MyInitedObj() = default; uint32_t myVal{0}; };

    MyObj w{};

    MyObj x; // causes an error because x is not initialized
    // compiler output (gcc):
    // error: local variable ‘x’ not initialized and not marked 
    // ‘[[uninit]]’ under the ‘std::init’ profile

    MyObj y [[uninit]]; // does not cause an error, explicitly marked uninit

    MyInitedObj z; // does not cause an error, implicitly init'ed

    // below causes an error because y has not been initialized: 
    // compiler output (gcc):
    // error: member ‘square(int)::MyObj::myVal’ of ‘y’ read 
    // before it is definitely assigned, under the ‘std::init’ profile
    if (y.myVal > 0) {
        std::println("Susquehanna");
    }
}

In cases where an object is conditionally initialized, the compiler will also ensure that the object is initialized in the path where it is used. Where objects may not be initialized before they are used, an error is raised, as seen below: 

// continuing inside square(int) from above 
if (std::rand() % 2 == 0) { 
    y = MyObj{}; 
} 
 
// below also causes error because y may not have been initialized. 
// every possible path must initialize 'y' before it can be used: 
// compiler output (gcc): 
// error: member ‘square(int)::MyObj::myVal’ of ‘y’ read  
// before it is definitely assigned, under the ‘std::init’ profile 
if (y.myVal > 0) { 
    std::println("Go Birds"); 
} 

This profile also extends to initialization of objects that a pointer refers to. It does so by requiring that the memory to which a pointer points is initialized before the pointer is passed to a function, unless the parameter is annotated otherwise. The uninitialized annotation can either be ref_to_uninit, which signals that the pointer points to uninitialized memory, or it can be must_init which requires the pointer point to uninitialized memory. must_init also requires that the pointer is initalized before the function exits. 

void MyFunc(MyObj* objPtr); // must point to initialized memory
void MyFuncNoInit(MyObj* objPtr [[ref_to_uninit]]); // must point to uninitialized memory
void MyFuncWillInit(MyObj* objPtr [[must_init]]); // must point to uninitialized memory,
                                                  // which this func will initialize

MyFunc(&y); //causes an error because y may not be initialized 
MyFuncNoInit(&w); //causes an error because w is initialized 

MyFuncWillInit(&y); 
MyFunc(&y); // Now works b/c y was init'ed by MyFuncWillInit
MyFuncNoInit(&y); // Now an error b/c y was init'ed by MyFuncWillInit

This profile also provides T* now_init(T* p [[ref_to_uninit]]) to explicitly mark the memory to which a pointer points as initialized and T* now_uninit [[ref_to_uninit]](T* p) to explicitly mark it as uninitialized.

 

Invalidation Profile 

The invalidation profile ensures that a reference to an object is not used after the object is destroyed (in other words, preventing dangling pointers). The profile handles not only raw pointers but also any way that an object can be directly identified. This includes, but is not limited to: references, lambdas with capture-by-references, and iterators. 

It achieves this by creating a new class of pointers called 'owners'. Each object can only have one owner but may have many pointers. No pointer to an owner may outlive that owner. Pointers that are not owners cannot delete. Owners must either be deleted or returned from a function before they go out of scope. 

[[profiles::enforce(std::invalidation)]]; 

uint32_t *owner_1 [[owner]] = new uint32_t {610}; // owner ptr
uint32_t *ptr_1 = owner_1; // normal ptr (owner: owner_1)

uint32_t *owner_2 [[owner]] = owner_1; // ownership transferred from owner_1
uint32_t *ptr_2 = owner_2; // normal ptr (owner: owner_2)

*ptr_1 = 401; // causes an error since ownership was xferred away
              // from owner_1
// compiler output (gcc):
// error: ‘owner_1’ is read here, after already being 
// consumed on every path reaching this point, under 
// the ‘std::invalidation’ profile

// both of the below work since they exist within the owner
*ptr_2 = 19428; 
*owner_2 = 19087; 

delete ptr_2; // causes an error because ptr_2 is not an owner
// compiler output (gcc):
// error: ‘delete’ of a pointer not marked 
// ‘[[owner]]’ not permitted under the ‘std::invalidation’
// profile

delete owner_2; // works b/c owner_2 is the owner pointer

// above must be called before the function returns. Otherwise:
// compiler output (gcc):
// error: ‘[[owner]]’ pointer ‘owner_2’ is never deleted or 
// passed on before the function returns, under the ‘std::invalidation’
// profile

One example provided by Stroustrup was handling an iterator into a vector that may be invalidated by reallocation. 

std::vector<uint32_t> nums = {1, 2, 3, 4, 5};

auto first = nums.begin();

nums.push_back(6); // may reallocate, invalidating iterators

*first = 0; // error since first may be invalidated by push_back
// compiler output (gcc):
// error: use of a value bound to ‘nums’, potentially 
// invalidated by an earlier mutation of ‘nums’, not 
// permitted under the ‘std::invalidation’ profile

Ranges Profile 

The ranges profile would ensure that you are not accessing an element in a list outside of the range through compile time and runtime checks. It would achieve this by preventing access to data where a range is not known (such as pointer arithmetic). 

Currently, no compiler implements support for the ranges profile. 

bool compare_span(uint32_t i, std::span<uint32_t> s, uint32_t* ptr) {
    uint32_t ptr_val = ptr[i]; // cannot be checked at run/compile time since length not specified
    uint32_t span_val = s[i]; // can be checked at runtime since span has a length

    return ptr_val == span_val;
}

Engaging Profiles 

There are 4 directives that control the enforcement of profiles: enforce, suppress, require, and exempt. 

profiles::enforce requires that a profile is enforced for a given module or scope. 

profiles::suppress suppresses requirements from a profile for a module or scope, similar to the unsafe keyword in other languages. For example: 

[[profiles::enforce(std::init)]]; 

uint32_t check() {
    uint32_t x [[uninit]];

    {
        [[profiles::suppress(std::init)]] // std::init won't be checked in this scope
        if (x > 10) { // no error!
            return 0;
        } 
    }

    if (x < 10) { // still an error, since x is not initialized! 
        return x;
    }
    return 0;
}

profiles::require and profiles::exempt are useful for requiring that external code implement support for a given profile, or for exempting code that doesn't support a profile but is necessary for your application to function. Having to explicitly mark a library in this manner makes it easier to review dependencies that may be causing unexpected behavior when debugging. 

profiles::require requires that an imported module or scope enforces a given profile. Modules will not be checked for any profiles that are applied through profiles::enforce, unlike headers, which will be checked against the importer's profiles. 

 

profiles::exempt is similar in behavior to profiles::suppress, but applies to an entire included header file.

[[profiles::enforce(std::init)]]; 
[[profiles::exempt(std::init, quote_header: "mozart.h")]]; 

// will be checked against std::init
#include "amfleet.h"

// will not be checked against any profile
#include "mozart.h"

// only checked against invalidation, not init
import maestro [[profiles::require(std::invalidation)]];

Additional Notes 

In the future, it's expected that more profiles will be created by users who want to provide their own guarantees about behavior that may be domain specific. Stroustrup specifically mentioned future profiles that could explicitly define arithmetic behavior such as overflows. 

Susquehanna's engineers look forward to profiles, which will eliminate entire classes of bugs that currently cost many engineering hours to resolve. It's imperative to us that some profiles (invalidation, init) can be applied at compile time with no runtime penalty. This is critical in an industry where every microsecond matters. We're also encouraged that this will help the C++ ecosystem continue to thrive. 

There's currently very limited compiler support for profiles. The most feature-complete at the time of writing is the gcc fork by villevoutilainen, which supports both std::invalidation and std::init (with some quirks). It is available on GitHub. There's also a clang fork that implements basic support for std::init available on GitHub. 

Many code samples were adapted from Stroustrup's talk, which you can watch on YouTube. 

Slides

 

Towards a complete contract-assertion facility for C++, Timur Doumler 

Timur Doumler gave an overview of the current state of contracts, starting with which components were voted into C++26. C++26 contains the first set of functionality for contracts, allowing developers to add preconditions and postconditions to functions as well as a new facility for asserting conditions within a function (like C asserts or static_assert). The syntax is: 

constexpr uint32_t score_game(std::span<const uint32_t> rolls)
    pre(rolls.size() <= 21) 
    post(r: r <= 300) // r is return value, 300 is max bowling score
{
    // rolls not yet thrown count as 0
    auto get_roll = [&](size_t j) -> uint32_t { return j < rolls.size() ? rolls[j] : 0; };

    uint32_t score = 0;
    for (auto i = 0uz, frame = 0uz; frame < 10 && i < rolls.size(); frame++) {
        uint32_t pins = get_roll(i) == 10 ? 10 : get_roll(i) + get_roll(i + 1);
        contract_assert(pins <= 10);
        score += pins == 10 ? get_roll(i) + get_roll(i + 1) + get_roll(i + 2) : pins;
        i += get_roll(i) == 10 ? 1 : 2;
    }
    return score;
}

Contracts are checks to ensure code is behaving as expected. They are checked at runtime and during constant evaluation; in the latter case, an enforced violation becomes a compile error. For runtime checks, the action taken when a contract assertion is violated is chosen at build time (via a compiler flag). The options are: ignore, observe, enforce, and quick enforce. The matrix below describes their behavior: 

 Action 

 Terminates 

 Calls Violation   Handler 

 ignore 

 

 

 observe 

 

 X 

 enforce 

 X 

 X 

 quick enforce 

 X 

 

 

Doumler then discussed contract features coming in future versions of C++ (currently targeting C++29). 

 

P3097: Pre and post assertions on virtual functions 

Currently, the largest shortcoming with C++26 contracts is the inability to have pre and post conditions with virtual functions. P3097 closes this gap by adding support for contracts on virtual functions. Preconditions and postconditions can be specified on the base function, the intermediate overriders, and the final overrider. Two sets of conditions are evaluated: those on the function named at the call site (determined by the type of the pointer/reference used) and those on the final overrider. Conditions on intermediate overriders are skipped. When the object is accessed directly, only its conditions are evaluated. 

The example below shows which conditions are checked in which cases. 

class Bicycle {
public:
    virtual void ShiftRearGear(uint32_t gear)
        pre(gear > 0)
        pre(gear < 8) {} // classic 8 gear rear freewheel 
};

class ShimanoBicycle : public Bicycle {
public:
    void ShiftRearGear(uint32_t gear) override
        pre(gear < 13) // new shimano 13 speed rear cassette
        pre(gear % 2 == 0) {}
};

class CannondaleBicycle : public ShimanoBicycle {
public:
    void ShiftRearGear(uint32_t gear) override
        pre(gear < 10) {} // uses an older shimano groupset
};

CannondaleBicycle cb{};
Bicycle& b = cb;
ShimanoBicycle& s = cb;

cb.ShiftRearGear(9); // works, only evaluated against final overrider

b.ShiftRearGear(9); // violation: Bicycle's precondition requires gear < 8
b.ShiftRearGear(7); // works: Bicycle and Cannondale preconditions are checked,
                    // Shimano precondition is skipped

s.ShiftRearGear(9); // violation: Shimano's precondition requires even gear
s.ShiftRearGear(8); // works: Shimano and Cannondale preconditions are checked,
                    // Bicycle's gear < 8 is skipped

P3099: User-defined error messages 

The diagnostic messages provided by the C++26 contracts implementation provide very basic diagnostic information and don't surface domain-specific details. To reach feature parity with assert and static_assert, P3099 adds syntax for specifying a custom error message. Taking our bowling example from above, we can move the comment explaining that 300 is the max bowling score into a message that is included in the runtime output. 

uint32_t score_game(std::span<const uint32_t> rolls)
    pre(rolls.size() <= 21, "A bowling game can only have a maximum of 21 rolls") 
    post(r: r <= 300, "300 is the maximum score in bowling") 

P3290: Explicitly Triggered Contract Violations 

There are many existing libraries that aim to validate conditions in code. This proposal exposes the contract-violation handler such that these libraries could invoke the contract-violation handler instead of using their own error-handling logic. This allows for one handler to process all these violations instead of one application having N different handlers due to consuming many libraries. 

This proposal would also allow the C assert macro to invoke the contract-violation handler instead of outputting a message and terminating the program. This would be opt-in to preserve existing behavior. 

 

P3098: Postcondition Captures 

P3098 provides a mechanism for functions that mutate state to capture the original value of that state for use in a postcondition. In C++26 contracts, developers would need to add extra boilerplate to capture the value and then use a contract_assert to check the value. P3098 allows for this condition to be more elegantly expressed in the function declaration. Consider the following example, where we mutate score when calculating the new score: 

// C++26 contracts
uint32_t score_single_frame_cpp26(uint32_t score, uint32_t pins)
    pre(pins <= 10) 
    post (r: r <= 300) {
    uint32_t old_score = score;

    score += pins;
    contract_assert(score >= old_score);

    return score;
}

// with P3098
uint32_t score_single_frame_capture(uint32_t score, uint32_t pins)
    pre(pins <= 10) 
    post (r: r <= 300)
    post [pre_score = score] (r: r >= pre_score) {

    score += pins;
    return score;
}

P3400: Assertion-Control Objects aka Labels 

P3400 allows conditions to be annotated by labels. This allows for easy identification of certain classes of contract conditions (i.e., you can group different kinds of checks together to get a better view of your codebase). Labels can also control how contracts are evaluated. For instance, you could disable a set of contract checks that may be too expensive for a release build, while development builds retain them. It could also let certain modules use different contract-violation-handling logic. 

void check_item(uint32_t* list, uint32_t index, uint32_t size)
    pre<range_check>(index < size)

Note that the syntax has yet to be finalized. 

P3100: Undefined Behavior Implicit Contract Assertions 

Like profiles, P3100 aims to provide safety guarantees and address undefined behavior. It does this by adding implicit contract assertions to built-in operations. For example, indexing into an array would have an implicit precondition that the index is within bounds. To preserve backward compatibility, these checks are configurable through the choice of contract evaluation semantic. Combined with labels as discussed above, users could turn on specific UB checks gradually as they update their codebase. 

 

Additional Notes 

Our engineers believe that the upcoming changes to contracts in future C++ versions will supplement the existing C++26 behavior in a way that makes contracts much more approachable for real-world use cases. 

Slides

 

A Duck Does More Than Quack: Synthesizing VTables with C++26 Reflection, Ryan Keane 

Ryan Keane presented rjk::duck, his type-erasure library that heavily uses C++26 reflection. Most type-erasure libraries require macro usage and large amounts of boilerplate, while the power of reflection enables rjk::duck to be lightweight. An example usage is below: 

struct [[=rjk::trait]] PriceUpdate {
    auto symbol() const -> std::string_view;
    auto price() const -> uint32_t;
};

// unrelated types: no common base class, no virtual functions
// may come from different libraries
std::vector<rjk::duck<PriceUpdate>> feed;
feed.emplace_back(ArcaQuote{"SPY", 50125});
feed.emplace_back(ItchTrade{"SPY", 50127});
feed.emplace_back(FixMessage{"SPY", 50130});

for (const auto& update : feed)
    if (update.price() > theo)
        std::println("{} above theo", update.symbol());

Note: rjk::trait was removed in a newer library version, but is shown above to illustrate the use of a C++26 annotation 

 

C++26 Reflection 

To enable the above, duck generates a vtable for the trait type using reflection. 

[[=value]] is a new feature in C++26 that allows developers to attach a constant value (an annotation) to a declaration, which reflection code can then inspect. For example, the rjk::trait annotation above specifies that the struct is a duck type and should have a vtable generated for it. 

^^ is the reflection operator (also known as the 'cat ears' operator). It can be used on a code entity to produce a single reflection value of type std::meta::info, a handle to that entity's metadata. 

[: <var> :] is the splice operator; var must be a std::meta::info. It turns the metadata object back into a piece of code. 

Below is a small example of how ^^, [: :], and some functions from std::meta work together to provide reflection: 

struct MyStruct { uint32_t x = 100; uint32_t y = 200; private: uint32_t z = 300; };

int main() {
    MyStruct s;

    // context lets you choose if you can see private/internal members
    constexpr auto ctx = std::meta::access_context::unprivileged();

    // template for expands at compile time
    template for (constexpr auto member :
                  std::define_static_array( // necessary to iterate over values 
                    std::meta::nonstatic_data_members_of(^^MyStruct, ctx)
                  )) {
        std::println("{} = {}", std::meta::identifier_of(member), s.[: member :]);
    }
}

This produces the output: 

x = 100
y = 200

std::meta::define_aggregate allows for the definition of a new aggregate type at compile time using reflection: 

struct State;

consteval
{
    std::meta::info members[]
    {
        std::meta::data_member_spec(^^std::string, {.name = "name"}),
        std::meta::data_member_spec(^^int, {.name = "population"})
    };

    std::meta::define_aggregate(^^State, members);
}

... and creates the type:

struct State {
    std::string name;
    int population;
};

VTable Generation 

Using reflection, duck iterates over the functions in an rjk::trait type and uses define_aggregate to create a vtable with pointers for the respective functions. Our above rjk::trait would create a vtable type that looks like:

struct vtable {
    /* simplified: copy, move, etc omitted; real library uses const void* for const functions */
    std::string_view (*symbol)(void* self);
    uint32_t         (*price)(void* self);
};

For every function in the trait, duck generates a wrapper_base class whose only member is an empty wrapper_functor named after the function. The functor is marked [[no_unique_address]] so that it permits overlap. All the wrappers are combined into wrapper_generator::type, which the final duck type inherits from, giving duck price() and symbol() methods: 

 

Chart adapted from Keane's presentation 

To perform the vtable lookup, the wrapper_functor needs to find the duck it belongs to. Initially, Keane stored a back-pointer in each functor. To reduce overhead, duck now uses pointer-interconvertibility (for standard-layout types, a pointer to a struct's first member can be reinterpret_cast to a pointer to the struct in a well-defined manner since they share an address). Since the functor is the only member of its wrapper_base, it can cast its own this to the wrapper_base and static_cast down to the duck that inherits from it. This gives it access to the vtable. At the bottom of the chain, the vtable slot for each stored type points to a small generated erased_call function that casts the void* back to the real type and invokes its member. 

Additional Notes 

std::protocol (P4148), which has an interface and functionality like rjk::duck, has been proposed for C++29. Keane also noted that P3294 "Code Injection with Token Sequences" would allow for creating functions, negating the need for rjk::duck's functor and inheritance trick. 

Much of the above content was adapted from examples in Keane's talk and blog post, which goes into more detail. 

We were impressed with how many C++ features could be composed together to provide a clean type-erasure implementation. Type erasure is a common sight in the trading industry, as we often use a single interface to handle messages from many different exchanges and protocols. 

Slides

 

std::simd Without Compromise: SIMD in C++26 and Beyond…, Ruslan Arutyunyan & Daniel Towner

Ruslan Arutyunyan and Daniel Towner from Intel presented an overview of std::simd, the new SIMD library in the C++26 standard library, and Intel's open-source implementation. SIMD provides powerful tools to manipulate large amounts of data in parallel for performance gains. Previously, engaging this behavior was hard to do; developers had three options: 
•    Let the compiler auto-optimize, which may not achieve intended goals; 
•    Write intrinsics or inline asm to generate SIMD instructions, which can be hard to read, maintain, and port; or 
•    Use existing SIMD libraries, which means trading portability for capability, using fragmented APIs, and working outside the standard. 

What makes std::simd worth watching is that it finally brings portable, readable data-parallel code into the C++ standard library. Instead of choosing between opaque auto-vectorization and architecture-locked intrinsics, you get an abstraction that looks like ordinary C++ and still maps to real SIMD hardware. 

std::simd provides two new types for writing SIMD code: simd::vec and simd::mask. These types can be interacted with through std::simd's algorithms, reductions, and permutations. For example: 

namespace simd = std::simd;

simd::mask<int32_t> getDecisions(simd::vec<int32_t> theos_cents,
                                 simd::vec<int32_t> bbo_cents,
                                 simd::vec<int32_t> req_edge_cents)
{
    constexpr simd::vec<int32_t> MIN_THEO_CENTS(0);
    constexpr simd::vec<int32_t> MAX_THEO_CENTS(1'000'000);
    auto theos = simd::clamp(theos_cents, MIN_THEO_CENTS, MAX_THEO_CENTS);
    return theos >= bbo_cents + req_edge_cents;
}

 

The equivalent written with intrinsics is much more cluttered and is bound to AVX2: 

uint32_t getDecisions(__m256i theos_cents,
                      __m256i bbo_cents,
                      __m256i req_edge_cents)
{
    const __m256i MIN_THEO_CENTS = _mm256_set1_epi32(0);
    const __m256i MAX_THEO_CENTS = _mm256_set1_epi32(1'000'000);
    __m256i theos = _mm256_min_epi32(
        _mm256_max_epi32(theos_cents, MIN_THEO_CENTS), MAX_THEO_CENTS);
    __m256i needed = _mm256_add_epi32(bbo_cents, req_edge_cents);
    __m256i ge = _mm256_cmpeq_epi32(_mm256_max_epi32(theos, needed), theos);
    return _mm256_movemask_ps(_mm256_castsi256_ps(ge));
}

 

std::simd will generate the appropriate instructions with the native register width for the target microarchitecture, making it more easily portable. Now, you can write one SIMD implementation and have it work across architectures and processor generations with a simple recompile.
 
Performance-wise, Arutyunyan and Towner stated that it is typically as fast as hand-written SIMD code, and in some cases faster, as the compiler can perform additional optimizations on the code generated by std::simd. 

Arutyunyan and Towner also outlined several future work items for std::simd: 
•    User-defined element types (P2964): currently the element types are limited to a subset of arithmetic types. P2964 would allow user-defined types to be used in std::simd containers. 
•    simd::iota (P3319): add a simd::iota object that produces a vector of sequential elements, similar to std::views::iota. 
•    Better width-agnostic support: std::numeric_limits for vectors (P4209), and additional load/store/gather/scatter operations. 
•    Parallel algorithms integration (P0350): an execution::simd policy would allow algorithms such as std::for_each to operate on a simd::vec. 


We're excited to see first-class support for SIMD from the standard library. Currently, SIMD usage is heavily limited by the maintainability issues mentioned by Arutyunyan and Towner. In the future with std::simd, we believe everyone will be able to further integrate SIMD into their codebases, leading to general performance improvements across the board. 


Slides