Maven on C++23

Introduction

At Maven we are always excited to adopt a new version of C++. Each revision brings new features and improvements which make our lives as engineers easier and our code more expressive. Although C++23 is a smaller update than 20, partially due to the pandemic, there’s still plenty of new features to unwrap and get stuck into, let’s take a look at some of these.

Features

Deducing this

In C++ non-static member functions have an implicit “this” argument, which points to the invoking object. The “deducing this” paper extends the language to allow “this” to be explicitly declared in the method signature.

// before
class Foo
{
public:
    void doSomething()
    {
        std::cout << this->mValue << 'n';
    }
private:
    int mValue;
};
 
// after
class Foo
{
public:
    void doSomething(this Foo const& self, int i);
    {
        std::cout << self.mValue << 'n';
    }
private:
    int mValue;
};

By making the “this” explicit the member method can deduce the value category of the invoking expression. So the method can tell if it’s being called on an l-value or r-value and if it is const or volatile.

The main use case for this is code de-duplication, the paper is well worth a read and showcases its advantages. However there is one use case that is directly applicable to the Maven code base, the ability to remove the Curious Recurring Template Pattern (CRTP).

The following code snippet is a simplified version of a base class in our serialisation library.

template<typename DerivedType>
class WriterBase
{
public:
    template<concepts::String T>
    void serialize(T const& value)
    {
        derived().writeString(value.data(), value.size());
    }
 
private:
    DerivedType& derived() { return *static_cast<DerivedType*>(this); }
    DerivedType const& derived() const { return *static_cast<DerivedType const*>(this); }
};
 
class SimpleWriter : public WriterBase<SimpleWriter>
{
public:
    void write(char const *data, std::size_t size)
{
    // do actual writing here
}
 
};

But with explicit access to this:

class WriterBase
{
public:
    template <typename Self, typename T>
    void serialize(this Self &&self, T const& value)
    {
        self.writeString(value.data(), value.size());
    }
};
 
class SimpleWriter : public WriterBase
{
public:
    void write(char const *data, std::size_t size)
    {
        // do actual writing here
    }
 
};

It may not seem like a big change but:

  • We’ve removed the need for all the casting with the derived() methods
  • We have access to the derived type directly in the method that needs it
  • We’ve removed the need to template WriterBase

At Maven we pride ourselves in the way we leverage templates to solve problems generically and also to achieve better runtime performance. The cost we pay is higher compile times. So any reduction in templated code (without sacrificing performance) is welcome.

Monadic operations on std::optional

Std::optional is an important vocabulary type, facilitating expressive APIs that can be explicit with their error handling. Introduced in C++17 it saw no extension in C++20 (other than being made constexpr). C++23 brings new monadic operations to optional which enables better chaining of operations.

Imagine we have the following API:

void subscribe(std::optional<int> id);

The specifics of subscribe are unimportant, but we do know that we need an optional integer id. Now suppose we read that id as a string from a file, we might end up with the following situation

const std::optional<std::string> idString = readIdFromFile();
if (const auto id = idString ; id)
{
    subscribe(std::stoi(*id));
}

We’ve got to get the value, check if it’s empty and if not do a cast. The new transform method (which returns the result of an optional from a function if the optional has a value, or an empty optional) helps us collapse all this down

subscribe(readIdFromFile().transform([](const auto &s) {
    return std::stoi(s);
}));

This is so useful we already have a hand rolled version of this in our code base, which we can now look to replace with a standardised version, further simplifying our code.

Range improvements

One of the core improvements in C++20 was the introduction of ranges. C++23 extends its API with new functionality. We already use ranges extensively, but one welcome new addition is zip_transform. This takes a function object and one or more views and produces a view whose ith element is the result of calling the function object on the ith element of the input views. This is useful in the following scenario, say we have a collection of key value stores (e.g. std::vector<std::unordered_map>) and we have a collection of keys we want to remove from the key value stores.

A naive solution would look like this:

void process(auto const &toRemove, auto &dataStores)
{
    auto iter1 = toRemove.begin();
    auto iter2 = dataStores.begin();
 
    while (iter1 != toRemove.end())
    {
        iter2->erase(*iter1);
        ++iter1;
        ++iter2;
    }
}

This doesn’t feel like modern C++, we’re manually looping and doing our own iterator bookkeeping. However with the new ranges API:

bool process(auto const &toRemove, auto &dataStores)
{
    auto res = std::views::zip_transform(
        [](auto const &a, auto &b) { return b.erase(a); },
        toRemove,
        dataStores);
 
    return std::ranges::all_of(res, [](auto const &a) { return a == 1; });
}

As an added bonus we can also easily check if the erasure happened!

Conclusion

Keeping up to date allows us to fully leverage the language and stay at the bleeding edge of what is possible. We have a dockerised build system which reduces the friction for upgrading compilers. We’re currently building with gcc12 and clang14 and rolling out the clang15 image, so we are ready for the above new features as soon as they make their way into the compilers.

 

NY INTERN BLOG
Catching up with our NY intern
Settling into Maven life My name is Alejandra Freyre and I worked...
FPGA
FPGAs in Trading
What is an FPGA? A Field Programmable Gate Array, or FPGA, is...
JO'S JOURNEY BANER
A Mavenites Journey
I joined Maven 4 years ago as a Business Development Analyst....
POST-1
Maven’s June Chronicle
Markets are wrestling with the question as to which is the greater...
POST-3
Machine Learning in Algorithmic Trading
Algorithmic trading systems are sets of rules that automate the...
POST 7 V2
March Chronicle
Most of our discussions with counterparts over the last few weeks...