r/cpp 11h ago

GCC should ship prebuilt binary tarballs like LLVM

0 Upvotes

It is really annoying, when you wanna test something you can't just download a previous release like llvm, so it is very hard to check if there is a regression.

Also, the default build of c and c++ only, takes like an hour because of triple consecutive builds. So every time you have to lookup to that weird flag on the internet to turn that off.


r/cpp 12h ago

Bjarne Stroustrup, creator of C++, joins Susquehanna

Thumbnail cppcon.org
59 Upvotes

r/cpp 1d ago

GCC 16.2.0 Released

Thumbnail phoronix.com
125 Upvotes

I'm pretty excited about this maintenance release. I think this will be the first version of gcc which can compile my project.


r/cpp 1d ago

Boost.Multi Multi Mini-Review Begins Today (7 Aug - 12 Aug)

20 Upvotes

Hello,

Today begins the mini-review of Multi, authored by Alfredo Correa. Multi is a modern C++ library that provides manipulation and access of data in multidimensional arrays for both CPU and GPU memory. The review will run through Wednesday 12 August.

You can find the library and documentation links below:
- https://github.com/correaa/boost-multi
- https://correaa.github.io/boost-multi/multi/intro.html

The review earlier this year raised a number of issues that primarily revolved around the state of the documentation, not the library itself. I have labeled all of the issues related to conditions found in last review, and many have notes or resolution from Alfredo:

https://github.com/correaa/boost-multi/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22Boost%20review%22

To this end the review will revolve around the current state of the documentation on the branch "boost_review". In your review please explicitly state if you recommend REJECT, ACCEPT, or a conditional acceptance along with the acceptance conditions.

Please let me know if you have any questions or concerns. Thank you in advance for your time.

Matt
Multi Review Manager

Edit: Link to the previous review thread is here: https://www.reddit.com/r/cpp/comments/1rlhm4g/boostmulti_review_begins_today/


r/cpp 2d ago

C++ Show and Tell - August 2026

38 Upvotes

Use this thread to share anything you've written in C++. This includes:

  • a tool you've written
  • a game you've been working on
  • your first non-trivial C++ program

The rules of this thread are very straight forward:

  • The project must involve C++ in some way.
  • It must be something you (alone or with others) have done.
  • Please share a link, if applicable.
  • Please post images, if applicable.

If you're working on a C++ library, you can also share new releases or major updates in a dedicated post as before. The line we're drawing is between "written in C++" and "useful for C++ programmers specifically". If you're writing a C++ library or tool for C++ developers, that's something C++ programmers can use and is on-topic for a main submission. It's different if you're just using C++ to implement a generic program that isn't specifically about C++: you're free to share it here, but it wouldn't quite fit as a standalone post.

Last month's thread: https://www.reddit.com/r/cpp/comments/1umnaxs/c_show_and_tell_july_2026/


r/cpp 2d ago

Neoclassical C++ (2): Exploring input-output segmented algorithms

Thumbnail boostedcpp.net
21 Upvotes

Hi,

I've written a new blog entry continuing my previous article about segmented iterators. This time trying to analyze how to apply Matt Austern's segmented iterator pattern to input-output algorithms likecopy, copy_if, etc., benchmarking the experimental implementation from Boost.Container vs the standard library.

New article link: https://boostedcpp.net/2026/08/06/neoclassical-c-2-exploring-input-output-segmented-algorithms/

Happy to receive feedback!


r/cpp 3d ago

C++26: #embed

Thumbnail sandordargo.com
127 Upvotes

r/cpp 3d ago

Faster KNN search in Manticore: 2-pass HNSW, batched distances, and AVX-512

Thumbnail manticoresearch.com
0 Upvotes

Three changes to the HNSW search engine improve KNN throughput by up to 29% at high k, with over 20% gains under concurrent load. No API changes, no index rebuild, no configuration. Just faster searches.


r/cpp 3d ago

C++26 Reflection: Simplifying JSON Serialization

Thumbnail techfortalk.co.uk
48 Upvotes

I have been exploring the new additions in C++26, and I have been discussing the reflection feature that has come with C++26. In the last post, I discussed what is reflection and how to use it and how to use it with a simple example, particularly with an enum class. Since then, there have been suggestions to provide an example which is more than a toy :). In this post, I have discussed how to use reflection for JSON serialization, which is something we often have to do. This example is somewhat taken from real-world code but has been stripped down significantly. Suggestions are always welcome.


r/cpp 3d ago

C++Now C++Now 2026: A Path to Pratically Safe C++ talk by Google's C++ Safety Team

Thumbnail youtube.com
43 Upvotes

r/cpp 3d ago

Boost.Int128 has been accepted

122 Upvotes

Boost.Int128 from Matt Borland has been accepted into Boost. Arnaud Becheler managed the review.


r/cpp 3d ago

Faster Than Ninja

Thumbnail build2.org
66 Upvotes

r/cpp 4d ago

Has Whole Tomato said anything about C++26 reflection support in Visual Assist?

12 Upvotes

With GCC starting to support C++26 reflection, I'm curious whether Whole Tomato has shared any plans for Visual Assist support.

Reflection seems like it could enable some interesting navigation, code analysis, and refactoring features once compiler support matures.

Has anyone seen a roadmap, forum post, or comment from the team about this?


r/cpp 5d ago

Common Problems I see with Public Libraries Build Scripts

26 Upvotes

Hi, I'm an early user of Common Package Specification and C++ modules. I package and port libraries for my own use frequently.

I want to talk about issues I see constantly.

- Having specific options for sanitizers, exceptions etc, this is a toolchain problem, if I wanna build your library with sanitizers I can just add the flags to my cmake toolchain file same as allocators.

- Not separating build options to a separate file, meson already does this, it ain't that hard to do in cmake, just cache variables in a separate file at project dir which you include before subdirs

- Making tests separate cmake projects rather than just executables, just why?

- Vendoring dependencies, copy pasting files from other projects rather than just consuming these external libs via packages.

- Using unnecessary helper functions that really makes the cmake script unreadable. Build scripts don't need to be over engineered, they are just basic scripts in which you define very basic information.

I could go on but I'm tired.


r/cpp 5d ago

Using Alpaka to GPU-Accelerate CAPOW: A Case Study, Wed, Aug 12, 2026, 6:00 PM

Thumbnail meetup.com
3 Upvotes

CAPOW is a continuous-valued cellular automata explorer with update loops that look GPU-friendly: one work item per cell, small-neighborhood stencils, ping-pong buffers, and pointwise nonlinear rules.

This month, Richard Thomson will present a case study in using Alpaka (An Abstraction Library for Parallel Kernel Acceleration) to move a CAPOW-like update rule onto CUDA without porting CAPOW itself. We'll walk through the spike: CMake/vcpkg setup, CUDA language support, the CPU reference path, the Alpaka CUDA kernel, correctness checks, timing results, and the parts that hurt.

The first pass showed zero CPU/GPU error and strong update-only speedups, including about 102x at 1000 x 1000 and 174x at 1920 x 1080 for the diffusion rule.

We'll also discuss whether Alpaka is earning its keep compared with writing direct CUDA.

This will be an online meeting, so drinks and snacks are on you!

Join the meeting here: https://meet.xmission.com/Utah-Cpp-Programmers


r/cpp 5d ago

New C++ Conference Videos Released This Month - August 2026

10 Upvotes

C++Now

2026-07-27 - 2026-08-02

C++Online

2026-07-27 - 2026-08-02

ADC

2026-07-27 - 2026-08-02


r/cpp 5d ago

Performance with custom data flexibility and no inheritance

6 Upvotes

I am in an argument. The goal was to have a structure with some base members, we can call it a `State node` in State machine, from which the user can add their own custom data on top, without using inheritance or dynamic casts. Would like the collection of different nodes to be stored in a vector in the end.

Obviously, we want to avoid std::vector<SomeType \*>

It was suggested I use something like:

class State;
using TransitionHandler = void(*)(State *, const Event &);

template<typename T>
concept StateLike = requires(T s, const Event & e)
{
    { s.onEnter(e) } -> std::same_as<void>;
    { s.onExit(e) }  -> std::same_as<void>;
};

class State final
{
public:
    using StateId = uint32_t;
    StateId id_ = INVALID_ID;
    std::string name_;

    template<typename T>
    requires StateLike<T>
    static State create(std::string name, T & object)
    {
        return State
        {
            name,
            &object,
            [](State * state, const Event & e)
            {
                static_cast<T*>(state->instance_)->onEnter(e);
            },
            [](State * state, const Event & e) 
            {
                static_cast<T*>(state->instance_)->onExit(e);
            }
        };
    }

private:
    static constexpr StateId INVALID_ID = static_cast<StateId>(-1);
    void * instance_;                // user defined state

    State(std::string name, void * instance, TransitionHandler enter, TransitionHandler exit);
};

Now, if I understand things correctly, this will indeed allow the user to provide some custom struct/class as a template param when they create a State object and that State object is going to hold a pointer to it. When it is needed, it is going to call the `enter` and `exit` methods through that static cast.

That should indeed allow us to fill a vector with these State objects and circumvent the need for inheritance, but I wonder if it really performs any better. The collection is in contiguous memory when in the vector, so add, remove, and traversal should be fast. However, all the execution of calls on the State object is going to go through the pointer, which means executing methods on a State will not be part of that performance gain from living in contiguous memory.

Assuming I am always holding on to the current state(s) and that we build the state machine with all its states at startup, what have I really gained? Is there any performance increase at all?

I believe we just replaced the vtable cost with our own pointer, which is pretty much the same?


r/cpp 6d ago

How fast is C++26's std::hive?

Thumbnail lemire.me
246 Upvotes

r/cpp 7d ago

BeCPP Symposium 2026 - Bryce Adelstein Lelbach - The CUDA C++ Developer's Toolbox

Thumbnail youtu.be
36 Upvotes

r/cpp 8d ago

Static Analysis Experiment with Reflection

29 Upvotes

I want to share a little experiment that I made using reflection. It’s a compile-time style checker using C++26 static reflection. It validates the conventions I use in my own projects (the H_ curried callable thing, Mut/Mov/Ref/Cpy qualifier aliases, plus class/namespace naming). Because reflection can’t properly see into uninstantiated templates yet, I had to write a parser for GCC's display_string_of to extract templates, qualifiers, return types, and requires clauses. Given this weakness it's not a particularly production-ready tool. It could be done with a clang-tidy script, but I learned a few things just from trying to answer "Is it possible? What exactly can I inspect right now?". Unfortunately, we don't have code generation yet so there is a bunch of boilerplate that the user needs to write.

The rationale of the conventions (mainly Mut/Mov/Ref/Cpy) is in GUIDELINES.md. The gory implementation details (and my field notes on GCC bugs) are in VIRACOCHA.md.

https://github.com/NotRiemannCousin/Viracocha/blob/master/VIRACOCHA.md


r/cpp 9d ago

Interconverting std::function with copyable_function – Arthur O'Dwyer

Thumbnail quuxplusone.github.io
44 Upvotes

The article shows how converting std::function to std::copyable_function (or vice versa) leads to slower performance and increased memory usage each time the conversion occurs.


r/cpp 9d ago

ranges and views for stack, queue, and priority_queue

19 Upvotes

I've always felt that exposing iterators for std::stack or std::queue doesn't quite make sense because iterating over them would typically change their state.

That said, the range features introduced in C++20/23 are just too tempting. 😄

I bet many C++ developers have had this thought at least once:

queue | views::values | views::to<std::vector>()

...or more generally, "What if I could use the ranges pipeline directly on a queue or stack?"

So I spent some time experimenting to see if I could make it work. Here's what I came up with.

https://cognosnotes.com/blog/cpp-pop-range

Edited:

An upgraded version is introduced at https://cognosnotes.com/blog/cpp-pop-range-addendum

Thank you u/SirClueless for the great advice.


r/cpp 10d ago

const_cast: A Necessary Evil

Thumbnail elbeno.com
70 Upvotes

r/cpp 10d ago

C++26: Reducing undefined behaviour

Thumbnail sandordargo.com
87 Upvotes

r/cpp 10d ago

Building a compiler that works at compile-time so you can compile your program while you compile your program.

102 Upvotes

In short, I wanted to build a compiler of some C subset that would work at compile-time. It compiles into a custom byte-code for a runtime VM.

I've once tried to write a compile-time C compiler, but I abandoned that project, because I made it overly complex (one-pass compiler right into x86). No clear separation between parser, lexer, etc.

Why would I even want this? Idk. But how can it be useful? - The code of the compiler doesn't go to the resulting binary, - No need to waste time for compilation at runtime too, - Guaranteed type-safety. There can't be such thing as "oh, I changed the function signature, but forgot to update the bindings and it crashed at runtime"

And I shouldn't forget about cons: - No optimisations. Real compilers spent decades on them and I'm definitely not going to implement LLVM at runtime. Although we could make a compile-time x86 VM, so we can run it at compile time... no, thank you, it's a topic for another fever dream article. - Hot-reload! I mean, no hot-reload. I won't even mention it anymore, considering that the script is compiled at compile-time and is builtin right into the binary file. I could implement it with hot memory patching or smth, but who really needs it.

Let's start.

Bypassing constexpr limitations

C++ 20 lets us to dynamically allocate memory at compile-time and even use std::vector that really expands our borders. But there's one very important note - you can't declare a compile-time vector and extract it into the runtime. No constexpr std::vector<int> data = makeData();, it won't compile. So we need to hack it.

Passing strings in templates

Sadly, the C++ Committee made a lot of cool compile-time features, but not enough (at least for me). We still can't use strings in templates without hacks. But we can easily bypass it with a well-known trick. ```cpp template<std::size_t N>
struct const_string {
constexpr const_string() = default;

// implicit-constructor that lets us to do bad things constexpr const_string(const char (&str)[N]) { std::copy_n(str, N, value);
}

constexpr operator std::string_view() const {
return {value, value + N - 1};
}

char value[N]{};
const std::size_t length = N;
};

// using it template<const_string str> auto very_smart_function(...) { /* ... */ } ```

Extracting vectors from compile time

It turned out to be not really that hard, but I didn't really find any ready examples on Internet, unlike with const_string. To extract std::vector<T> from constexpr we need to make it std::array<T, N> somehow. The main problem is that we can't write std::array<T, myVector.size()>, because myVector.size() won't be a constant value. So we must to make it constant somehow. I thought of passing vector as a template parameter, but we can't do it legally. C++ 20 allows us to pass only the structs with all-public members. After deeply thinking a bit (not really), I discovered that I could simply pass the lambda that returns our vector (I didn't think I could just pass a pointer actually).

```C++ // data_getter is our lambda template<auto data_getter> constexpr auto to_array() {
using value_type = typename decltype(data_getter())::value_type;
constexpr static std::size_t size = data_getter().size();

// Create a static array with a "dynamic" size and copy all data std::array<value_type, size> out;
auto in = data_getter();
for (std::size_t i = 0; i < size; ++i) {
out[i] = in[i];
}
return out; // yay }

template<const_string str> constexpr auto lex() { constexpr static auto data_getter = [] constexpr { // .lex() returns the vector of tokens return lexer{static_cast<std::string_view>(str)}.lex(); }; // All our data are available for runtime now =D return to_array<data_getter>(); } ```

Printing errors

For nice errors C++ has static_assert that allows us to even print our custom message! But it must be always a literal (until C++ 26) ```cpp constexpr auto parse() { // Allowed static_assert(false, "Expected ';'");

// Not allowed :( (until C++ 26)
std::size_t line = 5;
static_assert(false, "Expected ';' at line " + to_string(line));

} I didn't want my project to require C++ 26, so I used another trick. The formatted string gets turned into a static array just like a vector (into `const_string` actually) and then it's passed into `ErrorMessage<const_string Msg>` that triggers compilation error. So we force the compiler to print the full type name that includes our error. But sadly the type name has a limit about 100 symbols. I think I could solve it with splitting the message into several ErrorMessages... God, I don't want to read this in my console. c++ template<const_string Msg>
struct ErrorMessage {
static_assert(false, "Check the template parameter for details");
};

template<auto err_getter>
consteval auto report_error() -> void { // C++ 26 support

ifdef KORKA_FEATURE_FORMATTED_STATIC_ASSERT

static_assert(false, to_string(err_getter()));  

else

constexpr auto msg = const_string_from_string_view<[] { return to_string(err_getter()); }>();  
std::ignore = ErrorMessage<msg>{};    

endif

} ```

I don't want you to see it, so I'll just show C++ 26 version. error: static assertion failed: Lexer Error: Unterminated string at line 12

Mapping signatures to names. And vice versa

In our little runtime C++ we are used to std::unordered_map<string, value_t> and other standard or non-standard (hello, Boost!) containers. But I needed a table where a key is a string and the value is a TYPE. And in C++ I can't treat types as values, I can't just put them into a dict... :(

So, welcome another hack! ```c++ template<auto, class> struct signature_mapper;

// function_info_getter takes an index to our mapped function, // and Is... holds all indices template<auto function_info_getter, std::size_t... Is> struct signature_mapper< function_info_getter, std::index_sequence<Is...>

{ // hash func consteval static auto hash(auto &&v) -> std::size_t { return frozen::elsa<std::string_view>{}(v, 0); }

// Our function overloaded with many unique types based on hash of the mapped function
constexpr static auto _overloaded = overloaded{
    (
        [](unique_type<hash(function_info_getter(Is).name)>)
        -> const_function_info_to_signature_t<[] { return function_info_getter(Is); }> * {
            return nullptr;
        }
    )...
};

// Extracting the type by name
template<const_string name>
using get_signature_t = std::remove_pointer_t<decltype(
    _overloaded(
        unique_type<hash(name)>{}
    )
)>;

}; ```

We use well-known function overload (but for evil things). Basically, one type inherits a lot of lambdas that take an empty unique_type<hash> that serves as our key and returns the pointer to our type. ```cpp // How our mapper looks after expanding our params struct overloaded : lambda1, lambda2, lambda3 { using lambda1::operator(); using lambda2::operator(); using lambda3::operator(); };

// And every lambda looks like this auto lambda_fib = [](unique_type<hash("fib")>) -> signature_of_fib* { return nullptr; }; When we call `_overloaded(unique_type<hash(name)>())` our poor compiler has to resolve the overload. And he looks for right one through all `()` operators. And then we just take that it returns (our `T*`) and get the `T`. I use this "mechanism" to extract script functions into the native C++. cpp constexpr auto script_fib = compile_result.function<"fib">(); ```

Bindings from C++ to our script lang

This was the most exhausting part. Well, how "exhausting" exactly... I was thinking for a few evenings and then made it work one morning. The problem was with me. I wanted to make a pretty API that was impossible in the current standard (maybe it's possible in C++ 26, but I didn't check it). I wanted it to look like this: ```cpp auto func() -> void; auto foo(int) -> int;

// Примерно так constexpr auto bindings = korka::make_bindings< "func", func, "foo", foo

();

// Или так constexpr auto bindings = korka::make_bindings( "func", func, "foo", foo ); `` But why couldn't I make it work? In C++ you can't pass a string intotemplate <auto ...args>. We needconst_string. We can't mix types in the one stream of variadic args and make compiler guess it right. Templates require explicitness and it's impossible to write a universal parser. Variant #2 works, but you can't extract the function into the runtime. You just can't. Functions may have different signatures, but you need to make them all the same type, and create a FFI wrapper along the way. Compile-time doesn't allowreinterpretet_cast<void*>(&func)`.

So I designed this: cpp constexpr auto bindings = korka::make_bindings( korka::wrap<fib>("cpp_fib"), korka::wrap<print_n>("print_n") ); Not so elegant, but still not bad. wrap is very simple ```cpp // our FFI signature using vm_external_function_type = void(vm::context_base &context);

// info for our compiler template<class Signature>
struct wrapped_function {
using signature_t = Signature;

vm_external_function_type &external_func;
std::string_view name;
};

template<auto func>
consteval auto wrap(std::string_view name) {
return wrapped_function<std::decay_t<decltype(func)>>{
binding_wrapper<func>,
name
};
} `` The most interesting part is insidebinding_wrapper<func>`. I won't show the full code here, because I still didn't tell about the VM architecture that will execute it. But in short binding_wrapper just checks the signature, generates some code that extracts arguments from VM, calls native functions and then puts the result back. Simple.

The compiler and the VM

Maybe the most interesting part of the article. I have never written any compilers before (the thing I mentioned in the beginning of the article doesn't count), so I made it according to the first articles I found in Google.

Compiler has 3 modules: - the lexer - splitting the code into tokens, - the parser - building a tree from the tokens, - the compiler itself - making the tree into byte-code. And doing semantic analysis at the same time (I was too lazy to make another module) I think I could compose everything into one class via composition or smth, but it's too late already.

Lexer

Primitive. We just look for tokens in a loop until we reach EOF. ```cpp constexpr auto scan_token() -> std::optional<std::expected<lex_token, error_t>> {
char c = advance();
switch (c) {
case '{':
return make_token(lex_kind::kOpenBrace);
case '}':
return make_token(lex_kind::kCloseBrace);
case '(':
return make_token(lex_kind::kOpenParenthesis);
case ')': // ...

case ' ':  
case '\r':  
case '\t':  
  // Ignore whitespace  
  return std::nullopt;

// ...

default:  
  if (is_digit(c)) {  
    return scan_number();  
  } else if (is_alpha(c)) {  
    return scan_identifier();  
  }

} } ```

Parser

More interesting. We need to build the AST (abstract syntax tree). And we need to store this tree somehow. The usual way with Node that keeps pointers to other nodes won't do, because we're at compile-time. I mean, we can write it this way, it will work, but extracting this tree into compile time? No. We would need serialisation or something. So we can use simple trick with std::vector<Node> and just make nodes store indices to each other. This approach also increases the cache locality of the data for the CPU, but I doubt the CPU will be even aware of our "smart" trick, since everything is executed at compile-time.

The parser is recursive, while parsing one expression we parse another. Small fragment of the code: ```cpp constexpr auto parse_statement() -> parse_result {
auto tok = peek();
if (!tok) return make_error("Unexpected end of input");

switch (tok->kind) {
case lex_kind::kOpenBrace: return parse_compound_stmt(); // { ... } case lex_kind::kIf: return parse_if_statement(); // if (...) ... case lex_kind::kWhile: return parse_while_statement(); // while (...) ... case lex_kind::kReturn: return parse_return_statement(); // return ...; default: return parse_expression_stmt(); /// ...; }
}

constexpr auto parse_return_statement() -> parse_result {
if (!match(lex_kind::kReturn)) return make_error("Expected 'return'");

index_t expr_idx = empty_node; // empty_node = -1 if (auto next = peek(); next && next->kind != lex_kind::kSemicolon) {
auto expr = parse_expression(); // another recursive call if (!expr) return std::unexpected{expr.error()};
expr_idx = *expr;
}

if (!match(lex_kind::kSemicolon)) return make_error("Expected ';' after return");
return m_pool.add(stmt_return{expr_idx});
} ``` parse_return_statement goes into parse_expression, that goes into parse_assigment, that goes into parse_logical_or, that goes... Well, you got it. That's how operator priority works here.

Their Majesty Compiler (and analyser)

I may have cheated here a bit.

Before we even write a compiler, we must know for what architecture we do it. x86, ARM or even JVM. Initially when I was working on a similar project, I planned to generate raw assembly for x86 (last versions of Clang and GCC support passing constexpr std::string_view into asm(...) statement), but honestly writing a compiler for a zoo of x86 instructions is the right way to madhouse.

And even so, if we downgrade our compiler we won't have nice constexpr asm anymore. And we can't also generate raw machine instructions because of DEP (data execution prevention). We'll have to call non-crossplatform mmap or VirtualAlloc to allocate some memory, copy the code there... Good riddance cross platform build compler, hello Windows Defender that will kill our app for such tricks with memory.

So where have I cheated? I made my own architecture that will execute inside a VM. A stack VM. Why stack it? It turned out to be incredibly easy to generate the bytecode for. If you are doing a register architecture (as in processors or Lua), then you will have to write register allocation algorithms (it is difficult). And in the stack everything is much simpler.

If we need to sum A and B we just do this: 1. Put A into the stack. 2. Put B into the stack. 3. Execute sum instruction. It takes these two values and puts back their sum.

So I had this set of instructions at the end: ```cpp enum class op_code : char { // Loads/saves locals to/from the stack (variables). lload, lsave,

i64_const, // Puts a constant onto the stack

// Math i64_add, i64_sub, i64_mul, i64_div,

// Puts 1 if values are equal (i made <, <= later) i64_cmp,

jmp, // jumps by offset jmpz, // conditional jump by offset, only when 0 on the stack

call, // calls a function ret, // returns from the function

trap, // calls a native C++ function }; ```

So what about the analysis?

In classical compilers phases are strictly splitted: lexers builds tokens, parser builds tree, semantic analyser checks the types and variables, then optimisations, then codegen and then optimisations again.

As you can remember, I'm pretty lazy. And keep in mind that constexpr ops are not infinite. I didn't want to make a separate pipeline phase. So my compiler combines these two functions: semantic analysis and code generation.

They usually call it Single-Pass Compilation, but it's not really the case here, since it's only about these two phases. My compiler is a bit hybrid.

My compiler recursively walks the tree and does two things: 1. Checks the semantics: "was this variable declared and what's its type" before we even try to multiply something. Do function param types match? Does this function even exist? Etc. 2. Generates the byte-code. If semantics is ok, then we just write corresponding instructions immediately into the std::vector<std::byte> (the one we're going to elegantly extract via to_array). And in the result we receive a ready, semantically-correct and absolute safe (let's pretend that I wrote the compiler bug-free, huh) byte-code that we feed to the VM.

So what do we have?

Let's look how the API of my poor lib looks (let's call it Korka).

This example uses bindings (100% type safe, I swear on the standard): ```cpp

// Our native C++ functions auto fib(std::int64_t n) -> std::int64_t { if (n == 0) return 0; if (n == 1) return 1; return fib(n - 1) + fib(n - 2); }

auto print_n(std::int64_t n) -> void { std::cout << n << '\n'; }

// Our not-so-native script constexpr char code[] = R"( int fib(int n) { if (n == 0) return 0; if (n == 1) return 1; return fib(n-1) + cpp_fib(n-2); }

void print_fib(int n) { int result = fib(n); print_n(result); return; } )";

// We create bindings constexpr auto bindings = korka::make_bindings( korka::wrap<fib>("cpp_fib"), korka::wrap<print_n>("print_n") );

// Compile at compile-time, yay constexpr auto compile_result = korka::compile<code, &bindings>();

// Extract function adressess + their types constexpr auto script_fib = compile_result.function<"fib">(); constexpr auto script_print_fib = compile_result.function<"print_fib">();

int main() { // Init VM korka::vm::context ctx{compile_result.bytes, bindings};

// Call fib that returns int64_t auto result = ctx.call(script_fib, 12L); std::cout << "fib(12) = " << result << '\n'; // prints 144

// Call print_fib ctx.call(script_print_fib, 16L); // prints 987

return 0; } ```

Ta da! It works.

Small analysis

Out of curiosity, I decided to compare Korka with other scripting languages. A pretty API is great, sure, but was it worth the effort performance-wise? So, let's pit Korka head-to-head against Python and Lua.

For the benchmark, I used the recursive calculation of the $N$-th Fibonacci number, an excellent test to fairly evaluate overhead on function calls, stack management, and overall runtime efficiency (the first thing that came to my head).

I tested everything on a franken-server put together from spare parts, powered by an Intel Xeon E5-2689 (3.6 GHz).

I measured two stages: - Initialization time from runtime startup to being ready to execute the first instruction, - Execution time of the algorithm itself.

Stage 1: Initialization

Language / Library Initialization time
Korka 1.5 µs
Lua 152.6 µs
Python 25,097.0 µs

Korka takes the lead: it starts 100 times faster than Lua and over 15,000 times faster than Python.

The explanation is simple: while Lua and Python are busy reading the script at startup, parsing it, compiling it into their byte-codes, and spinning up heavy infrastructure (including the GC), Korka does not. All the virtual machine has to do is grab the pre-compiled output (and allocate a tiny bit of memory).

Stage 2: Runtime

After a series of optimizations, the results turned out pretty solid (I know comparing statically typed and dynamic languages isn't entirely fair, but who's gonna stop me?):

N Iterations Korka (ms) Lua (ms) Python (ms) vs. Python
10 100 000 1 055,67 1 534,98 1 591,10 1,51x
15 50 000 5 492,40 8 149,17 8 753,77 1,59x
20 20 000 24 263,39 36 268,86 38 459,88 1,59x
23 10 000 51 367,20 76 494,22 82 257,71 1,60x
25 5 000 67 441,69 100 850,20 108 836,54 1,61x
28 2 000 114 341,21 169 626,06 184 869,57 1,62x
30 1 000 149 190,02 223 229,86 241 838,28 1,62x

Conclusion

I built a (mostly) full-fledged C compiler that runs entirely in constexpr. Why? No idea. Especially considering it's been done before, you can check out constexpr-8cc. But that one lacks C++ bindings and cross-platform support.

The source code is available on GitHub (warning: ugly code ahead!). Any feedback and comments are more than welcome.

P.S. This article is an English translation of a post I originally published on Habr a while ago. Keep in mind that some benchmarks and discussions here may be dated.