C++ - Reddit
227 subscribers
48 photos
8 videos
25.2K links
Stay up-to-date with everything C++!
Content directly fetched from the subreddit just for you.

Join our group for discussions : @programminginc

Powered by : @r_channels
Download Telegram
P1689's current status is blocking module adoption and implementation - how should this work?

There is a significant "clash of philosophies" regarding Header Units in the standard proposal for module dependency scanning P1689 (it's not standard yet because it doesn't belong to the language standard and the whole ecosystem is thrown to trash by now but it's de facto) that seems to be a major blocker for universal tooling support.

# The Problem

When scanning a file that uses header units, how should the dependency graph be constructed? Consider this scenario:

// a.hh
import "b.hh";

// b.hh
// (whatever)

// c.cc
import "a.hh";

When we scan c.cc, what should the scanner output?

Option 1: The "Module" Model (Opaque/Non-transitive) The scanner reports that c.cc requires a.hh. It stops there. The build system is then responsible for scanning a.hh separately to discover it needs b.hh.

Rationale: This treats a header unit exactly like a named module. It keeps the build DAG clean and follows the logic that `import` is an encapsulated dependency.

Option 2: The "Header" Model (Transitive/
Include-like) The scanner resolves the whole tree and reports that `c.cc` requires both `a.hh` and `b.hh`.

Rationale: Header units are still headers. They can export macros and preprocessor state. Importing a.hh is semantically similar to including it, so the scanner should resolve everything as early as possible (most likely using traditional -I paths), or the impact on the importing translation unit is not clear.

# Current Implementation Chaos

Right now, the "Big Three" are all over the place, making it impossible to write a universal build rule:

1. Clang (clang-scan-deps): Currently lacks support for header unit scanning.
2. GCC (-M -Mmodules): It essentially deadlocks. It aborts if the Compiled Module Interface (CMI) of the imported header unit isn't already there. But we are scanning specifically to find out what we need to build!
3. MSVC: Follows Option 2. It resolves and reports every level of header units using traditional include-style lookup and aborts if the physical header files cannot be found.

# The Two Core Questions

1. What is the scanning strategy? Should import "a.hh" be an opaque entry as it is in the DAG, or should the scanner be forced to look through it to find b.hh?

2. Looking-up-wise, is import "header" a fancy #include or a module?

If it's a fancy include: Compilers should use `-I` (include paths) to resolve them during the scan. Then we think of other ways to consume their CMIs during the compilation.
If it's a module: They should be found via module-mapping mechanics (like MSVC's /reference or GCC's module mapper).

# Why this matters

We can't have a universal dependency scanning format (P1689) if every compiler requires a different set of filesystem preconditions to successfully scan a file, or if each of them has their own philosophy for scanning things.

If you are a build system maintainer or a compiler dev, how do you see this being resolved? Should header units be forced into the "Module" mold for the sake of implementation clarity, or must we accept that they are "Legacy+" and require full textual resolution?

I'd love to hear some thoughts before this (hopefully) gets addressed in a future revision of the proposal.

https://redd.it/1qx7zex
@r_cpp
"override members" idea as a gateway to UFCS (language evolution)

(UFCS backgrounder: https://isocpp.org/files/papers/N4174.pdf )

I have two functions that tell me if a string contains the
characters of a particular integer. They're called hasInt and intIn.
(intIn is inspired by the python keyword in.)
They looks like this:

bool hasInt(const string s, int n)// does s have n?
{
return s.contains(tostring(n));
}

bool intIn(int n, const string s)// is n in s?
{
return s.contains(to
string(n));
}

It would be convenient if I could add hasInt as a member function to std::string:

bool string::hasInt(int n)
{
return ::hasInt(this, n);
}

Then I could use "member syntax" to call the function, like `text.hasInt(123)`.

Of course, that's not possible, because then I'd be changing the header files
in the standard libraries.

Here's an idea for a new language feature:
let's use the `override` keyword to allow us to "inject" member functions
into an existing class, without modifying the class definition. So the code:

override bool string::hasInt(int n)
{
return ::hasInt(
this, n);
}

will (in effect) add hasInt as a member function to string.

Thus, this "override member function" feature has a syntax like:

ReturnType ClassName::function(args){...etc...}

HOWEVER..... what if ClassName doesn't necessarily need to be a class, and could be
other types? Then you open the door to override members like:

override bool int::intIn(const string s)
{
return ::intIn(this, s);
}

Which allows code like `(123).intIn(text)`.

This is halfway to UFCS!

Using some macro magic and helper templates, we could define a
MAKE_UFCS macro to convert a non-member function into a member function:

#define MAKE_UFCS(f) \
override \
retType(f) argType1(f)::f(argType2(f) x)\
{ \
return f(
this, x); \
}

Thus the non-member functions hasInt and intIn could be "opted in" to UFCS
by the macro calls:

MAKEUFCS(hasInt);
MAKE
UFCS(intIn);

Or maybe, if this override-to-UFCS is useful enough, the override feature can be
applied to a collection of functions at once, like:

override hasInt, intIn;

or

override {
#include <cstdlib>
}

To UFCS-ify an entire header file at the same time.

EDIT: this idea would be similar to Scala's "Extension Methods": https://docs.scala-lang.org/scala3/book/ca-extension-methods.html

or C#'s "Extension Members": https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods

https://redd.it/1qyikcg
@r_cpp
MigrationManager::GetInstance();
manager.CreateMigrationHistory();
size_t applied = manager.ApplyPendingMigrations();
```

Supports rollbacks, dry-run preview, checksum verification, and distributed locking for safe concurrent deployments.

---

## Backup & Restore

Full database backup/restore with progress reporting:

```cpp
#include <Lightweight/SqlBackup.hpp>

// Backup to compressed archive (multi-threaded)
SqlBackup::Backup(
"backup.zip",
connectionString,
4, // concurrent workers
progressManager,
"", // schema
"*", // table filter (glob)
{}, // retry settings
{ .method = CompressionMethod::Zstd, .level = 6 }
);

// Restore
SqlBackup::Restore("backup.zip", connectionString, 4, progressManager);
```

Preserves indexes, foreign keys (including composite), and supports table filtering.

---

## Supported Databases

- Microsoft SQL Server
- PostgreSQL
- SQLite3

Works anywhere ODBC works (Windows, Linux, macOS).

---

## What's Next

We're actively developing and would love feedback. The library is production-ready for our use cases, but we're always looking to improve the API and add features.

We also consider abstracting away ODBC such that it could support non-ODBC databases like SQLite3 directly without the ODBC layer. That's a longer-term goal, but definitely a goal.

We currently focus on SQL tooling (migrations and backup/restore) as both are quite young additions that are still evolving.

Questions and PRs welcome!

https://redd.it/1r0co80
@r_cpp
Corosio Beta - coroutine-native networking for C++20

We are releasing the Corosio beta - a coroutine-native networking library for C++20 built by the C++ Alliance. It is the successor to Boost.Asio, designed from the ground up for coroutines.

**What is it?**

Corosio provides TCP sockets, acceptors, TLS streams, timers, and DNS resolution. Every operation is an awaitable. You write co\_await and the library handles executor affinity, cancellation, and frame allocation. No callbacks. No futures. No sender/receiver.

It is built on Capy, a coroutine I/O foundation that ships with Corosio. Capy provides the task types, buffer sequences, stream concepts, and execution model. The two libraries have no dependencies outside the standard library.

**An echo server in 45 lines:**

#include <boost/capy.hpp>
#include <boost/corosio.hpp>

namespace corosio = boost::corosio;
namespace capy = boost::capy;

capy::task<> echo_session(corosio::tcp_socket sock)
{
char buf[1024];
for (;;)
{
auto [ec, n] = co_await sock.read_some(
capy::mutable_buffer(buf, sizeof(buf)));

auto [wec, wn] = co_await capy::write(
sock, capy::const_buffer(buf, n));

if (ec)
break;
if (wec)
break;
}
sock.close();
}

capy::task<> accept_loop(
corosio::tcp_acceptor& acc,
corosio::io_context& ioc)
{
for (;;)
{
corosio::tcp_socket peer(ioc);
auto [ec] = co_await acc.accept(peer);
if (ec)
continue;
capy::run_async(ioc.get_executor())(echo_session(std::move(peer)));
}
}

int main()
{
corosio::io_context ioc;
corosio::tcp_acceptor acc(ioc, corosio::endpoint(8080));
capy::run_async(ioc.get_executor())(accept_loop(acc, ioc));
ioc.run();
}

**Features:**

* Coroutine-only - every I/O operation is an awaitable, no callbacks
* TCP sockets, acceptors, TLS streams, timers, DNS resolution
* Cross-platform: Windows (IOCP), Linux (epoll), macOS/FreeBSD (kqueue)
* Type-erased streams - write any\_stream& and accept any stream type. Compile once, link anywhere. No template explosion.
* Zero steady-state heap allocations after warmup
* Automatic executor affinity - your coroutine always resumes on the right thread
* Automatic stop token propagation - cancel at the top, everything below stops Buffer sequences with byte-level manipulation (slice, front, consuming\_buffers, circular buffers)
* Concurrency primitives: strand, thread\_pool, async\_mutex, async\_event, when\_all, when\_any Forward-flow allocator control for coroutine frames
* C++20: GCC 12+, Clang 17+, MSVC 14.34+

**Get it:**

git clone https://github.com/cppalliance/corosio.git
cd corosio
cmake -S . -B build -G Ninja
cmake --build build

No dependencies. Capy is fetched automatically.

Or use CMake FetchContent in your project:

include(FetchContent)
FetchContent_Declare(corosio
GIT_REPOSITORY https://github.com/cppalliance/corosio.git
GIT_TAG develop
GIT_SHALLOW TRUE)
FetchContent_MakeAvailable(corosio)
target_link_libraries(my_app Boost::corosio)

**Links:**

* Corosio: [https://github.com/cppalliance/corosio](https://github.com/cppalliance/corosio)
* Corosio docs: [https://develop.corosio.cpp.al/](https://develop.corosio.cpp.al/)
* Capy: [https://github.com/cppalliance/capy](https://github.com/cppalliance/capy)
* Capy docs: [https://develop.capy.cpp.al/](https://develop.capy.cpp.al/)

**What’s next:**

HTTP, WebSocket, and high-level server libraries are in development on the same foundation. Corosio is heading for Boost formal review. We want your feedback.

https://redd.it/1rqykml
@r_cpp
Modern declarative EDSL for graphic user interface in C++? Not QML or XAML.

Hello everyone,

I have been investigating lately (after using NiceGUI for a real project) and learning Jetpack Compose (not writing any real code with it, would do it with Jetpack Multiplatform if ever).

I am really impressed by Jetpack Compose approach, especially UDF.

I thought there could possibly not be a better way than MVVM tbh, after using it for years.

But after seeing how declarative composition inside the language, not with XAML or QML can be done, I am sold in the amount of boilerplate that can be saved, plus the better integration when the dsl is in the host language.

So I wanted to mention I found this, which I think could be a good start for some experiments, on top of wxWidgets: https://github.com/rmpowell77/wxUI

I think it has a talk in CppCon2025 here: https://www.youtube.com/watch?v=xu4pI72zlO4

Kudos to the author for bringing something like this to C++! Definitely useful.

I would like to hear your opinions on this style of EDSL GUI embedding and pros/cons you find for those of you who do GUI programming all the time.

Also, wild idea: with the power of compile-time C++ programming and C++26 reflection, it would be possible to get existing xaml interfaces and convert it into regular C++ at compile-time via #embed or #include and not even changing the EDSL itself and making it directly embedded and reusable in C++? That would be plenty useful.


https://redd.it/1ry51fg
@r_cpp
I built a C++ integer-to-string library based on a new AVX-512 paper

I built a small C++ integer-to-string conversion library based on a new paper by Jael Champagne Gareau and Daniel Lemire, "Converting an Integer to a Decimal String in Under Two Nanoseconds":

- Project: https://github.com/simditoa/simditoa
- Paper: https://arxiv.org/abs/2604.26019

The paper looks at decimal formatting for integers, which shows up in logging, JSON/CSV/XML serialization, database output, and other places where numbers eventually become text. The interesting part, and the part I wanted to experiment with, is that it uses AVX-512 IFMA instructions to extract multiple decimal digits in parallel, avoiding the usual repeated division/modulo loop and avoiding large lookup tables.

The library exposes a small to_chars-style API:

#include "simditoa.h"

char buf[simditoa::MAX_DIGITS + 1];
size_t len = simditoa::to_chars(12345, buf);
buf[len] = '\0';


Current project shape:

- C++17
- int64_t and uint64_t support
- AVX-512 IFMA + VBMI path for supported x86-64 CPUs
- portable scalar fallback
- CMake package/install support
- tests for edge cases, digit lengths, and randomized values
- a simple benchmark against std::to_chars

The README benchmark currently shows simditoa::to_chars at about 15.82 ns/int versus 36.35 ns/int for std::to_chars on the tested setup, roughly 2.3x faster in that run. The paper reports stronger results for its full algorithm and benchmark suite, including single-core performance ahead of other tested methods, but my repo should be treated as a compact implementation based on the paper rather than a full reproduction of every variant in it.

The core trick is neat: for 8-digit chunks, it uses AVX-512 IFMA with precomputed constants based on floor(2^52 / 10^k) to compute digit positions in parallel, then gathers the digit bytes with AVX-512 byte permutation. Larger values are split into chunks.


https://redd.it/1t2ny62
@r_cpp
Are you satisfied with the current state of C++ CLI parsers?

There are already many excellent C++ CLI parsers out there, but most of them still revolve around mutable runtime builder APIs.

Most existing parsers look something like this:

CLI::App app{"example"};

std::string output;
bool verbose = false;
std::vector<std::string> inputs;

app.add_option("-o,--output", output, "output file");
app.add_flag("-v,--verbose", verbose, "verbose mode");
app.add_option("inputs", inputs, "input files")->required();

CLI11_PARSE(app, argc, argv);

Why are duplicate option names still runtime errors in C++ CLI parsers? Where is the compile-time validation?

Why is the command schema still built through runtime mutation? Many CLI schemas are effectively static. Why not treat them as a static schema and let the compiler enforce it?

One of the things I love about C++ is its ability to express intent and constraints in the type system and let the compiler enforce them.

But many C++ CLI parsers still rely heavily on runtime mutation and stringly-typed APIs.

Rust has `clap`, which is a great typed CLI parser. So what about C++?

So I wanted to explore what that direction could look like in modern C++20.

**I built a new C++ CLI parser** that takes a different approach:

#include <cli/cli.hh>

struct Args {
cli::Flag<"verbose", 'v'> verbose;
cli::StringOption<"output", 'o'> output;
cli::Positional<std::string, cli::nargs::one_or_more> inputs;
};

using namespace std;

auto main(int argc, char** argv) -> int {
// Returns parsed Args or exits with an error message.
const auto args = cli::parseOrExit<Args>(argc, argv);

std::cout << "verbose: " << args.verbose.value() << '\n';

if (args.output.has_value()) {
std::cout << "output: " << *args.output.value() << '\n';
}

for (const auto& input : args.inputs.value()) {
std::cout << input << '\n';
}
}

**Try this out on Godbolt:** [https://godbolt.org/z/53d8rEMoP](https://godbolt.org/z/53d8rEMoP)

The goal is to explore a C++20-native parser API where the command line is represented as a typed schema rather than a mutable runtime builder.

The interesting part for me is that this works in **plain C++20**, without reflection, macros, code generation, or external tooling.

Repository: [https://github.com/CLI20-dev/cli20](https://github.com/CLI20-dev/cli20)

Still early-stage. I'm mainly looking for feedback on API ergonomics, diagnostics, and the compile-time/runtime tradeoff.

https://redd.it/1t5fovs
@r_cpp
Poor man's defineaggregate

TLDR: [Try it on Compiler Explorer](
https://godbolt.org/z/sfczoP7hr).

----

While waiting for Clang to support `define
aggregate, I got curious about whether it's possible to do something similar in C++23. Turns out it *kinda* is.

### Rules:
- Only C++23 features;
- No external programs;
- No macros;
- Generated code should be similar to just using a
struct.

----

We start with some helper types:

#include <algorithm>
#include <array>
#include <concepts>
#include <functional>
#include <print>
#include <ranges>
#include <string_view>
#include <tuple>
#include <type_traits>

namespace detail
{

// See
type
template <typename T>
struct FieldType
{
using Type = T;
};

// Helper for using a string as a template parameter
template <std::size_t size>
struct ConstexprStringHelper
{
std::array<char, size - 1> array;

constexpr ConstexprStringHelper(const char (&c_array)[size])
{
std::copy_n(c_array, size - 1, std::begin(array));
}
};

// See
operator""field`
template <auto name>
struct FieldByName
{
};

We calculate the layout of our fake aggregate (sizes, alignment, offsets, ...) at compile time, like so:

// Layout information
template <auto... fields>
struct MetaAggregateInfo
{
static consteval auto calc
align(std::sizet offset, std::sizet align)
{
return (offset + align - 1) & ~(align - 1);
}

static constexpr std::array names{ std::stringview(fields.name)... };
static constexpr std::array sizes{ sizeof(typename decltype(fields)::Type)... };
static constexpr std::array aligns{ alignof(typename decltype(fields)::Type)... };
static constexpr auto max
align = std::ranges::max(aligns);
static constexpr auto offsets = {
std::removeconstt<decltype(sizes)> offsets;
std::sizet nextoffset = 0;

for (auto size, align, offset : std::views::zip(sizes, aligns, offsets))
{
offset = calcalign(nextoffset, align);
nextoffset = offset + size;
}

return offsets;
}();
static constexpr auto total
size = calcalign(offsets.back() + sizes.back(), maxalign);
};

I found it simpler to just use a partial specialization for the case where the aggregate has no members:

template <>
struct MetaAggregateInfo<>
{
static constexpr std::array<std::stringview, 0> names{};
static constexpr std::array<std::size
t, 0> sizes{};
static constexpr std::array<std::sizet, 0> aligns{};
static constexpr auto max
align = 1uz;
static constexpr std::array<std::sizet, 0> offsets{};
static constexpr auto total
size = 1uz;
};

}

A few more helpers:

// Use to declare the type of a field. See example below.
template <typename T>
constexpr detail::FieldType<T> type;

// Type and name of a field
template <typename TheType, std::sizet size>
struct Field
{
using Type = TheType;

detail::FieldType<TheType> type;
std::array<char, size> name;
};

// Use to declare the name of a field
template <detail::ConstexprStringHelper helper>
consteval auto operator""
name()
{
return helper.array;
}

// Use with operator to access a field by name
template <detail::ConstexprStringHelper helper>
consteval auto operator""field() -> detail::FieldByName<helper.array>
{
return {};
}

And now the meat of the code:

template <auto... fields>
class MetaAggregate
{
public:
static constexpr detail::MetaAggregateInfo<fields...> info{};

We define our constructors, copy/move operators and destructor. We use the offsets to get a pointer on which we can do a placement `new`. Other than that, this part is not very interesting.

MetaAggregate()
requires(std::default
initializable<typename decltype(fields)::Type> && ...)
{
std::apply(
& { (new (storage.data() + offset) decltype(fields)::Type(), ...); },
info.offsets
);
}

MetaAggregate(const MetaAggregate& other)
requires(std::copyconstructible<typename decltype(fields)::Type> && ...)
: MetaAggregate(other.refs())
{
}

MetaAggregate(MetaAggregate&& other)
requires(std::move
constructible<typename
Poor man's define_aggregate

TLDR: [Try it on Compiler Explorer](https://godbolt.org/z/sfczoP7hr).

----

While waiting for Clang to support `define_aggregate`, I got curious about whether it's possible to do something similar in C++23. Turns out it *kinda* is.

### Rules:
- Only C++23 features;
- No external programs;
- No macros;
- Generated code should be similar to just using a `struct`.

----

We start with some helper types:

#include <algorithm>
#include <array>
#include <concepts>
#include <functional>
#include <print>
#include <ranges>
#include <string_view>
#include <tuple>
#include <type_traits>

namespace detail
{

// See `type`
template <typename T>
struct FieldType
{
using Type = T;
};

// Helper for using a string as a template parameter
template <std::size_t size>
struct ConstexprStringHelper
{
std::array<char, size - 1> array;

constexpr ConstexprStringHelper(const char (&c_array)[size])
{
std::copy_n(c_array, size - 1, std::begin(array));
}
};

// See `operator""_field`
template <auto name>
struct FieldByName
{
};

We calculate the layout of our fake aggregate (sizes, alignment, offsets, ...) at compile time, like so:

// Layout information
template <auto... fields>
struct MetaAggregateInfo
{
static consteval auto calc_align(std::size_t offset, std::size_t align)
{
return (offset + align - 1) & ~(align - 1);
}

static constexpr std::array names{ std::string_view(fields.name)... };
static constexpr std::array sizes{ sizeof(typename decltype(fields)::Type)... };
static constexpr std::array aligns{ alignof(typename decltype(fields)::Type)... };
static constexpr auto max_align = std::ranges::max(aligns);
static constexpr auto offsets = [] {
std::remove_const_t<decltype(sizes)> offsets;
std::size_t next_offset = 0;

for (auto [size, align, offset] : std::views::zip(sizes, aligns, offsets))
{
offset = calc_align(next_offset, align);
next_offset = offset + size;
}

return offsets;
}();
static constexpr auto total_size = calc_align(offsets.back() + sizes.back(), max_align);
};

I found it simpler to just use a partial specialization for the case where the aggregate has no members:

template <>
struct MetaAggregateInfo<>
{
static constexpr std::array<std::string_view, 0> names{};
static constexpr std::array<std::size_t, 0> sizes{};
static constexpr std::array<std::size_t, 0> aligns{};
static constexpr auto max_align = 1uz;
static constexpr std::array<std::size_t, 0> offsets{};
static constexpr auto total_size = 1uz;
};

}

A few more helpers:

// Use to declare the type of a field. See example below.
template <typename T>
constexpr detail::FieldType<T> type;

// Type and name of a field
template <typename TheType, std::size_t size>
struct Field
{
using Type = TheType;

detail::FieldType<TheType> type;
std::array<char, size> name;
};

// Use to declare the name of a field
template <detail::ConstexprStringHelper helper>
consteval auto operator""_name()
{
return helper.array;
}

// Use with operator[] to access a field by name
template <detail::ConstexprStringHelper helper>
consteval auto operator""_field() -> detail::FieldByName<helper.array>
{
return {};
}

And now the meat of the code:

template <auto... fields>
class MetaAggregate
{
public:
static constexpr detail::MetaAggregateInfo<fields...> info{};

We define our constructors, copy/move operators and destructor. We use the offsets to get a pointer on which we can do a placement `new`. Other than that, this part is not very interesting.

MetaAggregate()
requires(std::default_initializable<typename decltype(fields)::Type> && ...)
{
std::apply(
[&](auto... offset) { (new (storage.data() + offset) decltype(fields)::Type(), ...); },
info.offsets
);
}

MetaAggregate(const MetaAggregate& other)
requires(std::copy_constructible<typename decltype(fields)::Type> && ...)
: MetaAggregate(other.refs())
{
}

MetaAggregate(MetaAggregate&& other)
requires(std::move_constructible<typename
fluxen: a single-header key-value store for C++20

I built fluxen to solve a problem I kept running into in my side projects. I wanted persistent key-value storage without pulling in a full database or dealing with CMake/linking.

To use fluxen, you just drop the header into your project and you have a persistent key-value database that you can learn to use in an afternoon, with no CMake, no dependencies, and no linking.

#include "fluxen.hpp"

fluxen::DB db("myapp.db");
db.put("username", "jim");

if (auto name = db.get("username")) {
std::cout << *name << "\n"; // jim
}

It supports strings, numbers, and any trivially copyable type without the need for manual serialization. Transactions batch writes into a single syscall and guarantee durability via fsync.

GitHub: [https://github.com/dvuvud/fluxen](https://github.com/dvuvud/fluxen)

Docs: [https://dvuvud.github.io/fluxen](https://dvuvud.github.io/fluxen)

https://redd.it/1t928n5
@r_cpp
Your C++ struct is the schema: a proto3 serializer in C++26 reflection

I built a header-only proto3 wire-format library with one constraint: no .proto files, no codegen, no descriptor runtime. The user writes a plain C++ struct, and that struct is the schema:

#include "proto3.hpp"
struct SearchRequest {
std::string query; // field 1
std::int32_t page_number; // field 2
std::int32_t results_per_page; // field 3
};
SearchRequest req{"hello", 1, 10};
std::string bytes = proto3::serialize(req); // -> proto3 wire bytes
SearchRequest back = proto3::deserialize<SearchRequest>(bytes);


blog: Your C++ struct is the schema: a proto3 serializer in C++26 reflection

github: struct\_proto26

https://redd.it/1tcrfz4
@r_cpp
Domain-Based C++ Logging With Nova

### Introducing Nova - A Deterministic C++ Logging Library With Domain-Based Routing

Repository: https://github.com/kmac-13/nova/
Benchmarks: https://github.com/kmac-13/nova/blob/main/docs/BENCHMARKS.md

I am pleased to announce the initial release of **Nova** - a modern C++ logging library focused on deterministic behavior, compile-time configurability, and flexible domain-based routing for systems ranging from hosted platforms down to bare-metal and safety-critical environments.

### Why Another Logging Library?

There are already several quality C++ logging libraries available. However, most logging libraries organize routing and filtering around severity levels and rely on global logger configuration or runtime string-based logging categories. Engineers are often forced to encode subsystem behavior into a limited set of severity levels while also considering which thresholds will be enabled in production. This also leads to situations where enabling debug logging for one subsystem effectively requires enabling debug logging across unrelated areas of the application.

Nova instead treats logging domains as compile-time types, allowing logging configuration and routing to directly reflect application structure rather than forcing subsystems into global severity categories.

Domains can represent subsystems, modules, interfaces, classes, libraries, or any other domain-specific concept, and each domain can be independently enabled, disabled, or routed without reliance on shared global configuration. Because domains are independent types rather than shared string identifiers, libraries can define their own logging domains without interfering with application or third-party logging configuration.

* type-defined logging domains - log against subsystems, modules, classes, libraries, or any other concept
* compile-time elimination of disabled domains - language-guaranteed in C++17, optimiser-dependent in C++11/14
* compile-time routing - avoids reliance on global logger registries or shared runtime configuration

Additional goals of the library include:

* simple pipeline and extensible API
* deterministic behavior - no heap, no exceptions, no RTTI
* support for platforms ranging from hosted systems down to bare-metal
* fast enough for demanding real-time and multithreaded workloads

Nova also includes Flare, an async-signal-safe crash and forensic logging component that writes structured diagnostic records directly to disk from signal handlers - without heap allocation, locks, or non-signal-safe C++ runtime features.

### Example

cpp
#include <nova.h>

// define a domain (can be any type)
struct MotionPlanner {};

// configure the domain with a name (MOTION), enabled state (true), and clock type (steadyNanosecs)
NOVA_LOGGER_TRAITS( MotionPlanner, MOTION, true, kmac::nova::TimestampHelper::steadyNanosecs );

int main()
{
// configure motion planner sink as mpSink
...

// bind the mpSink to the MotionPlanner logging domain
kmac::nova::ScopedConfigurator config;
config.bind< MotionPlanner >( &mpSink );

// log
NOVA_LOG( MotionPlanner ) << "Planning trajectory...";
}


Here we can see that the `MotionPlanner` domain is defined, the traits for the domain are configured, a target sink is bound to the domain, and logging is performed. In this example, the domain is a simple, empty struct, but a domain can be any type, including interface, abstract, or concrete classes. A domain can even be a specific class, and logging can be limited to the scope of that class.

Using types as logging domains enables compile-time routing, strong subsystem separation, and per-domain configuration and enablement. Disabled domains can be eliminated entirely by the compiler, and type names prevent the silent runtime failures that string-based routing can introduce.

Additionally, per-domain control means that enabling verbose logging for one subsystem has no effect on any other - there is no shared severity threshold to raise or lower across the entire application just to see
miniaudio compile times

i found this library called miniaudio, however instead of having precompiled dll files, its just the header and then the c file that really only includes the header, the header file has all of the definitions and declarations of everything in the library, it makes the compile times take a lot of time, can i compile the header file to a dll the c file is literally just:

#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio/miniaudio.h"

and the header file is like 4 megabytes

https://redd.it/1trk8yn
@r_cpp
Exotic CRTP: Enforcing Strict Interfaces Without Friends Using C++23 Explicit Object Parameters

I’ve been experimenting with CRTP and ended up with a variation that enforces a strict interface/implementation boundary without friend declarations. The goal was to eliminate boilerplate I frequently encountered when trying to encapsulate derived class methods.

The key idea is using C++23 explicit object parameters this + a small access wrapper type so implementations can only be called through the interface layer.

That was about two and a half months ago. Since, I’ve taken the time to better understand it and write an article about it, which you can find below.
As explained there, I refer to this approach as Exotic CRTP.

---

### Example

// Reference example of the pattern
// See: https://medium.com/@felixolivierdumas/exotic-crtp-rethinking-static-polymorphism-with-c-23-89f9e75e8ffd

#include <iostream>
#include <type_traits>
#include <utility>

namespace exotic {

template<typename... From>
struct crtp_access : From... {};

template<typename T>
constexpr decltype(auto) as_crtp(T&& obj) noexcept {
using crtp_access_t = crtp_access<std::remove_cvref_t<T>>;
return static_cast<crtp_access_t&&>(obj);
}

}

struct Base {
void interface(this auto&& self) {
exotic::as_crtp(self).implementation();
}
};

struct Derived : Base {
void implementation(this exotic::crtp_access<Derived> self) {
std::cout << "Derived implementation" << std::endl;
}
};

int main() {
Derived d;

d.interface(); // perfectly works

// d.implementation(); -> doesn't work, Derived only allows .interface()
}


---

Not sure yet if this is actually useful in real conditions or just a different way of structuring CRTP, but it seems to be genuinely powerful.

Full write-up here: https://medium.com/@felixolivierdumas/exotic-crtp-rethinking-static-polymorphism-with-c-23-89f9e75e8ffd

Curious how this compares to traditional CRTP + friend patterns in real codebases :)

https://redd.it/1tuv2pn
@r_cpp
LLMs with C and C++ - switch from HFT to AI lab

I'm a senior C++ dev recently started working with a neolab (who works with anthropic). Thought I would write some of observations i made.

* I had experimented with LLMs for a while before making the switch. LLMs fail a lot on C and C++ due to harder nature and powerful nature of language.
* Talking purely from benchmarks, languages like python and JS (the vibecoder's first language) have very hard benchmarks - think fixing actual bug that touches 3 different modules from scratch with access to tools like grep, cat and python3 executer.
* Whereas, benchmarks for C and C++ are at basic QA style questions. I have added a task from benchmark, which fable 5 could not solve.
* LLMs do not have understanding of latest ISO standards - for some reasons it switches to C++17 again and again
* LLMs are trash at template metaprogramming. Try debugging CRTP type of errors.

Looking at the efforts and progress, I am still not sure if we will see LLMs writing MRs to linux kernel. The approach they used for vibecoding languages do not care about memory safety, thread safety and performance much. It would be interesting to see the space evolve.

PS: a example from benchmark

PS2: i'm not associated with benchmark. they say the code is taken from real github issues.

Observe the following faulty CPP code snippet and error type list. Your task is to select the error type of the code based on the error list provided.
You only need to answer error type. Do not write anything else in your response.
For example, if the code snippet is missing a semicolon, Your output should be 'missing_colons'.
faulty code:
```cpp
#include <bits/stdc++.h>

int countPermutations(int n, int k, int qq[])
{
const int N = 505, P = 998244353;
int *q = new int[n + 10];
int m, dp[N][N], jc[N], f[N], ans;
memset(q, 0, sizeof(int) * (n + 1));
memset(dp, 0, sizeof(dp));
memset(jc, 0, sizeof(jc));
memset(f, 0, sizeof(f));
ans = 0;

for (int i = 1; i <= n; i++)
q[i] = qq[i - 1];
dp[0][0] = f[0] = 1;
for (int i = jc[0] = 1; i <= n; i++)
jc[i] = 1LL * jc[i - 1] * i % P;
for (int i = 1; i <= n; i++)
{
f[i] = jc[i];
for (int j = 1; j < i; j++)
f[i] = (f[i] + P - 1LL * f[j] * jc[i - j] % P) % P;
}
for (int i = 1; i <= n; i++)
{
for (int j = 0; j < i; j++)
for (int k = 1; k <= n; k++)
dp[i][k] = (dp[i][k] + dp[j][k - 1] * 1LL * f[i - j] % P) % P;
}
m = 0;
for (int i = 1; i <= n; i++)
if (q[i] > q[i + 1])
{
m = i;
break;
}
if (m == n)
{
for (int i = k; i <= n; i++)
ans = (ans + dp[n][i]) % P;
}
else
{
for (int i = m + 1; i <= n; i++)
{
if (i != m + 1 && (q[i - 1] > q[i] || q[i] < q[m]))
break;
int c = k + i - n - 1;
if (c >= 0)
ans = (ans + dp[m][c] * 1LL * jc[i - m - 1] % P) % P;
}
}
return ans;
}
```
error list:
['Delayed Execution', 'Improper HTML structure', 'Missing $', 'Missing mut', 'Misused := and =', 'Misused === and ==', 'Misused =>', 'Misused Macro Definition', 'Misused Spread Operator', 'Misused begin/end', 'Misused match', 'Misused var and val', 'Unused Variable', 'algorithm_error', 'condition_error', 'double_bug', 'faulty_indexing', 'function_error', 'html_unclosed_label', 'html_value_error', 'html_wrong_label', 'illegal_comment', 'illegal_indentation', 'illegal_keyword', 'illegal_separation', 'json_content_error', 'json_digital_leader_is_0', 'json_duplicate keys', 'json_struct_error', 'markdown_content_error', 'markdown_title_error', 'markdown_unclosed_error', 'missing_backtick', 'missing_colons', 'misused ==and=',
A tiny C++23 filesystem wrapper that returns std::expected instead of throwing

Hi everyone,

I’ve been working on a small C++23 header-only library called **expected_fs**:

https://github.com/MrZLeo/expected-fs

It’s a thin wrapper around `std::filesystem` where fallible operations return `std::expected<T, std::error_code>` instead of throwing `std::filesystem_error`.

The idea is not to replace `std::filesystem`, but to make filesystem code easier to compose in projects that prefer explicit error handling.

Example:

```cpp
#include <expected_fs/expected_fs.hpp>

#include <iostream>

int main() {
const auto size = expected_fs::file_size("data.txt");

if (!size) {
std::cerr << size.error().message() << '\n';
return 1;
}

std::cout << *size << '\n';
}
```

Most function names mirror `std::filesystem`, so usage should feel familiar:

```cpp
auto created = expected_fs::create_directories("out/cache");
auto copied = expected_fs::copy_file("input.txt", "out/input.txt");
auto removed = expected_fs::remove("out/input.txt");
```


It's simple and it just work. I’d love feedback on the API shape, naming, CMake packaging, and whether this feels useful to people who avoid exception-based filesystem handling.

Thanks for taking a look! :)

https://redd.it/1ubq20l
@r_cpp
Tired of mutex hell and spaghetti event handlers? Here's a single-threaded FSM methodology that keeps your async logic clean — forever. Just #include "uniflow.hpp".

We've all been there. A module that starts simple —
connect, send, wait for ack — and six months later looks like this:

bool connecting_, connected_, cmd_sent_, waiting_ack_, draining_, fault_;

void Update() {
if (estop_) {
connecting_ = false;
cmd_sent_ = false;
waiting_ack_ = false;
// forgot draining_ — ghost ack bug waiting to happen
...
}
if (!connected_) { ... }
else if (!cmd_sent_) { ... }
else if (waiting_ack_) { ... }
// where does fault_ get handled? what if estop_ and fault_ fire together?
}

Flags that must move in pairs. Resets that get forgotten.
E-stop logic that has to know about every stage.
Two developers write the same flow in completely different shapes.

**uniflow** keeps what works about the tick-based FSM — cooperative,
single-thread, no mutex — and fixes what doesn't.

Each stage becomes a named step function:

StepResult Step1_Connect() { device_.BeginConnect();
return Next(UF_FN(Step2_WaitConnected)); }

StepResult Step2_WaitConnected() { if (!device_.IsConnected()) return Stay();
return Next(UF_FN(Step3_WaitRequest)); }

StepResult Step3_WaitRequest() { if (!input_.HasRequest()) return Stay();
device_.Send(input_.Take());
return Next(UF_FN(Step4_WaitAck)); }

StepResult Step4_WaitAck() { if (device_.HasAck()) return Done();
return StayUntil(3000ms, UF_FN(Step5_Timeout)); }

One function = one state. Entry is explicit. Transitions are pinned in code.
No hidden jumps. No forgotten resets. Brace depth stays flat forever.

---

**How it works**

One `Runtime` owns one pump thread. Attach as many modules as you want.
The pump visits each module once per round — cooperative, round-robin.

uniflow::Runtime rt;
Flow_XAxis x_axis{rt};
Flow_YAxis y_axis{rt};
Flow_Conveyor conveyor{rt};

x_axis.ctx_home_.StartFlow(); // start homing X
y_axis.ctx_home_.StartFlow(); // start homing Y — simultaneously, no thread, no mutex

Modules on the same Runtime share the single-thread invariant.
No locks needed. Ever.

The pump adapts its sleep to the situation:
- Back-to-back transitions? No sleep, full speed.
- Polling with Stay()? 20ms yield. CPU near 0.
- All idle? 1ms — ready to wake immediately.

External event arrives? Call `rt.Wake()` from any thread. No waiting out a sleep cycle.

---

**Blocking work? No problem.**

Heavy I/O goes to the built-in thread pool via SubmitAsync.
The pump never blocks. Every other module keeps running.

StepResult Step1_Fetch() {
AsyncId job = SubmitAsync(UF_FN(DoFetch), 5000ms, url_);
return Next(UF_FN(Step2_Process), job);
}

StepResult Step2_Process(AsyncId job) {
auto r = AsyncResult<std::string>(job);
if (r.pending()) return StayUntil(5000ms, UF_FN(Step_Timeout));
if (!r.ok()) return Fail();
data_ = *r.return_value;
return Next(UF_FN(Step3_Save));
}

---

**Built-in tracing — zero instrumentation code**

Because every execution is "a step function was called once,"
one measurement point inside the pump sees everything:

[JobWorker] FLOW START
[JobWorker] Entry -> Step2_Validate elapsed=0.01ms
[JobWorker] ASYNC SUBMIT CallApi
[JobWorker] ASYNC DONE CallApi wait=124.38ms
[JobWorker] Step2_Validate -> Step3_WaitSave elapsed=124.42ms
[JobWorker] FLOW END DONE wall=143.21ms

Which step, how long, where it slowed down — visible without touching your logic code.

---

**What it's not**

- Not Boost.Asio — no new type system to learn, zero deps, doesn't absorb your objects
- Not C++20 coroutines — C++17, and style is
Rate my code!

#include <stdio.h> #include <string.h> #include <iostream> #include <filesystem> using namespace std; namespace fs = std::filesystem; using S = string; using H = const unsigned char; using F = void()(); using G = S()(); using D = void()(fs::path); using Q = void()(S); template<int N, int K> struct Z { S d(H h) { S r; int key = K; for(int i = 0; i < N; i++) { r += char(h[i] ^ key); key = (key 0x5F + 0x13) & 0xFF; } for(char &c : r) c = (c ^ 0x7F) - 3; return r; } }; S b(H h, int n) { S s; for(int i=0;i<n;i++) s+=char(hi); return s; } struct Obf { void o(S x) { cout << x; } S i() { S t; cin >> t; return t; } }; bool d(S p) { return fs::isdirectory(p); } void l(fs::path p) { for(auto& e : fs::directoryiterator(p)) cout << e.path().filename().string() << '\n'; } Obf ob; void e() { ob.o(b((H)"\x73\x6f\x72\x72\x79\x20\x77\x65\x20\x63\x6f\x75\x6c\x64\x20\x6e\x6f\x74\x20\x66\x69\x6e\x64\x20\x79\x6f\x75\x72\x20\x64\x69\x72\x65\x63\x74\x6f\x72\x79\x0a",38)); } void r(S p) { d(p) ? l(p) : e(); } S g() { return ob.i(); } F vf32; G gf8; D df8; Q qf8; void s1() { ob.o(Z<76, 0x37>().d((H)"\x4f\x5a\x58\x4e\x4b\x4e\x20\x60\x65\x5e\x52\x52\x20\x5d\x5e\x20\x5e\x5e\x63\x5e\x20\x57\x5d\x5e\x55\x5e\x5e\x5f\x5e\x20\x5e\x5f\x20\x5e\x5e\x5f\x20\x27\x2e\x2e\x27\x20\x5a\x5e\x5f\x20\x60\x5d\x5d\x5e\x27\x60\x20\x5f\x63\x5f\x5f\x5e\x5e\x5f\x20\x57\x5d\x5e\x55\x5e\x5e\x5f\x0a")); S x = gf0; x == "CD" ? vf9 : qf0; } void s2() { vf10; } void s3() { df0); } void s4(S p) { qf1; } void s5() { ob.o(b((H)"\x0a",1)); } int main() { vf0 = s1; vf9 = s3; vf10 = s2; df0 = l; qf0 = r; qf[1] = s4; gf[0] = g; vf0; s5(); return 0; }

https://redd.it/1ukn1v3
@r_cpp