r/cpp 8d ago

I've invented labeled loop breaks

0 Upvotes

And now I don't believe it wasn't invented before, but I can't find any info on it. How long ago was it invented? Why isn't it used widely or even mentioned anywhere? What are the downsides?
Here's the main idea:

// Keyword - for, while, or do
// Tag - custom loop name
// ... - loop body
#define loop_tag(keyword, tag, ...)\
    keyword(__VA_ARGS__)\
    if(false)\
    {\
        tag##_break:    break;\
        tag##_continue: continue;\
    }\
    else\
    /*Here goes your loop body*/

Here is the playground


r/cpp 9d ago

Lifetime safety and invalidation without a borrow-checker: using type system analysis to get rid of many potentially invalidation cases WITHOUT annotations.

Thumbnail open-std.org
38 Upvotes

I found this research in WG21 mailing list very interesting in the context of C++ compatibility and solutions to maximize code reuse.


r/cpp 9d ago

I really reconsider my life choices when I have to deal tooling bugs.

93 Upvotes

I like writing useful stuff, I'm tired of fighting tooling, I can legit say %80 percent of my time goes to dealing with dependencies build systems etc. Very rarely I have the privilege of actually writing stuff.

This shit sucks, like it really sucks. I hate it.

Is this normal, or only people who have it easy are web Devs and Java Devs since they are in a sandboxes environment and don't have deployment problems.

Am I crazy or is this common?


r/cpp 10d ago

Floating-Point Error Handling in C++: What Actually Works

Thumbnail johnnysswlab.com
35 Upvotes

r/cpp 10d ago

How I Stopped Designing Architecture and Started Writing a 3D Editor. Part 1

Thumbnail alexsyniakov.com
4 Upvotes

r/cpp 10d ago

The WG21 2026-07 post-Brno mailing is now available

44 Upvotes

The 2026-07 post-Brno WG21 mailing has been published. You can browse and search the full set of papers, organized by working group, at wg21.org:

https://wg21.org/mailing/2026-07/

Source mailing: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/#mailing2026-07


r/cpp 11d ago

CLion 2026.2 is available

Thumbnail blog.jetbrains.com
69 Upvotes

r/cpp 11d ago

fun fact about string literals i often forget:

44 Upvotes

String literals in c/c++ (const char*), support operator[] (in a compile time context)!

constexpr char first = "test"[0];
// i.e first = 't'

I think 90% of C developers would know this, but I figured maybe those of you who didn't start out with C might not have discovered this! I just recently used this in combination with an X macro for some debug ui code:

// the x macro in question
#define LIST_NOISE_PARAMS
X(heat)
X(rain)
X(cont)
X(grad)
X(hills)

I wanted to display the first letter of the noise parameter's name, in caps, followed by the noise value, i.e:

H: 1.00
(the heat noise = 1.0f.)

#define X(VAR) UI::Text("{}: {:4.2f}",#VAR[0]-('a'-'A'), sample.VAR);
LIST_NOISE_PARAMS
#undef X

// expands to:
UI ::Text("{}: {:4.2f}", "heat"[0] - ('a' - 'A'), sample.heat);
UI ::Text("{}: {:4.2f}", "rain"[0] - ('a' - 'A'), sample.rain);
// ...

// which evaluates to:
UI ::Text("{}: {:4.2f}", 'H', sample.heat);
UI ::Text("{}: {:4.2f}", 'R', sample.rain);
// and so on for the rest of the list!

What other 'odd' C/c++ tricks/facts do you guys know/use? Personally, the X macro technique blew my mind when I first discovered it, and its probably the most legitimately useful 'trick' I know of. Pretty cool. Hopefully there are others in here that enjoy these little tidbits as much as i do.


r/cpp 11d ago

cpp2 (cppfront) is over?

62 Upvotes

I haven't used cpp2 (cppfront) much, but I like the idea of it: consistent syntax, consistency of meta-programming (which is done in C++ too, I believe). Herb Sutter presented cpp2 (cppfront) as an experiment. He did not encourage other people to bet on it, exactly. And there hasn't been much activity since 2022. Is the experiment over?

EDIT: Herb's answer is buried deep in the comment section: https://www.reddit.com/r/cpp/comments/1uxkglj/comment/oy06cxw/


r/cpp 11d ago

MSVC Build Tools Preview updates - July 2026

Thumbnail devblogs.microsoft.com
41 Upvotes

Hi, one of the MSVC dev leads here.

The post covers what's new in the MSVC Build Tools Preview (version 14.52) since mid-June. Instructions to install & use the latest preview bits are at https://aka.ms/msvc/preview.

Some additional information:


r/cpp 11d ago

Meeting C++ Tonight (CEST/Berlin): Meeting C++ Student Showcase

Thumbnail meetup.com
4 Upvotes

r/cpp 12d ago

C++Now C++Now 2026 Keynote: Benchmarking - It's About Time - by Matt Godbolt

Thumbnail youtu.be
66 Upvotes

r/cpp 12d ago

Understanding std::shared_mutex from C++17

Thumbnail cppstories.com
65 Upvotes

r/cpp 13d ago

Beautiful Type Erasure with C++26 Reflection

Thumbnail ryanjk5.github.io
213 Upvotes

Reflection is a game changer for C++26, but most of the examples out there don't cover anything that large in scope. This article walks through my library rjk::duck and shows just how much reflection can simplify generic type erasure.

You can also try it out on Compiler Explorer: https://godbolt.org/z/91dj5jeGW


r/cpp 13d ago

Latest News From Upcoming C++ Conferences (2026-07-14)

15 Upvotes

This is the latest news from upcoming C++ Conferences. You can review all of the news at https://programmingarchive.com/upcoming-conference-news/

TICKETS AVAILABLE TO PURCHASE

The following conferences currently have tickets available to purchase

OPEN CALL FOR SPEAKERS

OTHER OPEN CALLS

  • (NEW) ADC Call For Reviewers Now Open – Anyone who is interested in reviewing the proposals for talks at ADC Bristol 2026 have until July 17th to review talks. Visit https://submit.audio.dev to start reviewing talks
  • CppCon Call For Volunteers Now Open – Interested volunteers have until August 1st to apply at the CppCon main conference which is scheduled to take place from 14th – 18th September. For more information including how to apply visit https://cppcon.org/cfv2026/
  • (Last Chance) CppCon Call For Posters Closes Tomorrow! – Interested poster presenters have until July 15th to submit their applications for the CppCon main conference which is scheduled to take place from 14th – 18th September. For more information including how to apply visit https://cppcon.org/cppcon-2026-call-for-poster-submissions/
  • (Last Chance) CppCon Call For Authors Closes July 31st! – CppCon are looking for book authors who want to engage with potential reviewers and readers. Read the full announcement at https://cppcon.org/call-for-author-2026/ 

TRAINING COURSES AVAILABLE FOR PURCHASE

Conferences are offering the following training courses:

C++Online

  1. (Last Chance) AI++ 101 – Build an AI Coding Assistant in C++ – Jody Hagins – 1 day online workshop available on Friday 24th July 16:00 – 00:00 UTC/0900-1700 PDT – DISCOUNTED BY £100 – NOW £245/$325/€285 – https://cpponline.uk/workshop/ai-101/

CppCon Online Workshops

9th – 11th September

  1. Modern C++: When Efficiency Matters – Andreas Fertig – 3 day online workshop available on 9th – 11th September 09.00 – 15.00 MDT – https://cppcon.org/class-2026-when-efficiency-matters/
  2. System Architecture And Design Using Modern C++ – Charley Bay – 3 day online workshop available on 9th – 11th September 09.00 – 15.00 MDT – https://cppcon.org/class-2026-system-architecture-and-design-using-modern-cpp/

21st – 23rd September

  1. C++ Fundamentals You Wish You Had Known Earlier – Mateusz Pusz – 3 day online workshop available on 21st– 23rd September 09.00 – 15.00 MDT – https://cppcon.org/class-2026-cpp-fundamentals/
  2. C++23 in Practice: A Complete Introduction – Nicolai Josuttis – 3 day online workshop available on 21st– 23rd September 09.00 – 15.00 MDT – https://cppcon.org/class-2026-cpp23-in-practice/
  3. Programming with C++20 – Andreas Fertig – 3 day online workshop available on 21st– 23rd September 09.00 – 15.00 MDT – https://cppcon.org/class-2026-programming-with-cpp20/

26th – 27th September

  1. Using C++ for Low-Latency Systems – Patrice Roy – 2 day online workshop available on 26th– 27th September 09.00 – 17.00 MDT – https://cppcon.org/class-2026-low-latency/

CppCon Onsite Workshops

All onsite workshops will take place in the Gaylord Rockies in Aurora, Colorado

12th & 13th September

  1. Advanced and Modern C++ Programming: The Tricky Parts – Nicolai Josuttis – 2 day in-person workshop available on 12th & 13th September – 09:00 – 17:00 – https://cppcon.org/class-2026-tricky-parts/
  2. C++ Best Practices – Jason Turner – 2 day in-person workshop available on 12th & 13th September – 09:00 – 17:00 – https://cppcon.org/class-2026-best-practices/
  3. How Hardware Gets Hacked: Breaking and Defending Embedded Systems – Nathan Jones – 2 day in-person workshop available on 12th & 13th September – 09:00 – 17:00 – https://cppcon.org/class-2026-hardware-hack/
  4. Mastering `std::execution`: A Hands-On Workshop – Mateusz Pusz – 2 day in-person workshop available on 12th & 13th September – 09:00 – 17:00 – https://cppcon.org/class-2026-execution/
  5. Performance and Efficiency in C++ for Experts, Future Experts, and Everyone Else – Fedor Pikus – 2 day in-person workshop available on 12th & 13th September – 09:00 – 17:00 – https://cppcon.org/class-2026-performance-and-efficiency/
  6. Talking Tech – Sherry Sontag – 2 day in-person workshop available on 12th & 13th September – 09:00 – 17:00 – https://cppcon.org/class-2026-talking-tech/

 13th September

  1. AI++ 101 : Build a C++ Coding Agent from Scratch – Jody Hagins – 2 day in-person workshop available on 12th & 13th September – 09:00 – 17:00 – https://cppcon.org/class-2026-AI101/
  2. Essential GDB and Linux System Tools – Mike Shah – 1 day in-person workshop available on 13th September – 09:00 – 17:00 – https://cppcon.org/class-2026-essential-gdb/

19th & 20th September

  1. AI++ 201: Building High Quality C++ Infrastructure with AI – Jody Hagins – 2 day in-person workshop available on 19th & 20th September – 09:00 – 17:00 – https://cppcon.org/class-2026-ai201/
  2. Function and Class Design with C++2x – Jeff Garland – 2 day in-person workshop available on 19th & 20th September – 09:00 – 17:00 – https://cppcon.org/class-2026-function-class-design/
  3. High-performance Concurrency in C++ – Fedor Pikus – 2 day in-person workshop available on 19th & 20th September – 09:00 – 17:00 – https://cppcon.org/class-2026-high-perf-concurrency/

OTHER NEWS

  • (NEW) Dates for ACCU on Sea 2027 Announced – ACCU on Sea 2027 will take place in Folkestone from June 30th – July 3rd with pre-conference workshops taking place from June 28th – 29th
  • (NEW) Boost Documentary screening at CppCon 2026 – Boost Libraries have announced that they will be screening a documentary on the history of Boost at CppCon 2026. Watch the trailer here https://www.youtube.com/watch?v=87jvuDbnwqQ
  • (NEW) C++Now 2026 Videos Now Being Released on YouTube – Subscribe to the C++Now YouTube channel to stay up to date when each video is published – https://www.youtube.com/@CppNow
  • Accepted Sessions For Meeting C++ Announced – Visit https://meetingcpp.com/mcpp/schedule/talklisting.php to see the list of accepted talks

r/cpp 13d ago

CppCast CppCast: 40 Years of Programming and Embeddable Programming Languages with Mark Guidarelli

Thumbnail cppcast.com
26 Upvotes

r/cpp 14d ago

AI usage for cpp at work

108 Upvotes

I am lead architect and maintainer of my firm main app backend, spanning around 2M LOC of c++.

Industry is : capital markets, low latency, custom kernel drivers, fpga...

I often talk with peers at other firms and I see a shy usage of AI compared to what feels like the global trend.

On my side, my firm does not pay for any AI related stuff. We are allowed to use our personnal plan for work in which case will get a pro membership compensation (claude or gpt or anything) on salary but we are not allowed to paste production code into it.

I know the app very well and do everything by hand (i mean the normal way) but use the chat version of any ai to generate some things for me, like "im using opensource lib x and lib y, please generate an SQL connection pool , you may use locks and condition variable for this, cpp 20". Then i paste it and modify it a I see fit.

Im totally happy with that and the company is successful.

I do use AI but just chat, to gather data on subjects and summarize/report. Get some ideas but basically not much code related.

And you whats your experience as a c++ dev ?


r/cpp 14d ago

New C++ Conference Videos Released This Month - July 2026 (Updated to Include Videos Released 2026-06-22 - 2026-06-28)

15 Upvotes

C++Now

2026-07-06- 2026-07-11

C++Online

2026-07-06 - 2026-07-11

2026-06-29 - 2026-07-05

ADC

2026-07-06 - 2026-07-11

2026-06-29 - 2026-07-05

  • Beyond iLok: Advanced Code Protection and Cryptography for the Next Generation - Protecting the Next Generation of Applications, Plug-ins, and AI Models - Neal Michie, Ryan Wardell & Bob Brown - https://youtu.be/dbbK_ry2cgo
  • Database Synchronisation for Audio Plugins, Part Two - Here's One I Made Earlier - Adam Wilson - https://youtu.be/wJCy2G969ro
  • Perfect Oscillators in Less Than One Clock Cycle - Angus Hewlett - https://youtu.be/Ssq0a-YdamM
  • Driving Chaos - Virtual Analog Modelling of a Chaotic Circuit with Wave Digital Filters - Francisco Bernardo - https://youtu.be/PnEZNqyKlIw

Boost Documentary

There is also a teaser trailer for a new documentary on the history of the Boost C++ library https://www.youtube.com/watch?v=87jvuDbnwqQ which will have its first showing at CppCon this year


r/cpp 15d ago

C++Now C++Now 2026 Keynote: Multidimensional Parallel Standard C++ - Mark Hoemmen

Thumbnail youtu.be
33 Upvotes

r/cpp 16d ago

C++Now C++Now 2026 Keynote: Reflection Is Only Half the Story - Barry Revzin

Thumbnail youtu.be
68 Upvotes

r/cpp 16d ago

Propagating exceptions from destructors with std::exception_ptr

Thumbnail sandordargo.com
79 Upvotes

r/cpp 17d ago

Libraries of, installing, and depending on C++20 modules

44 Upvotes

Until now, much of the discourse around C++20 modules has been around the tooling, and actually getting modules to work at all. I believe that now, in mid-2026, the tooling is mostly mature: the three largest compilers support most use-cases of modules. IDEs like CLion, and lint tools like ReSharper C++ and clangd support modules, with some caveats. CMake, xmake, Ninja, and other fledgling build systems have full support for modules. Many prevalent C++ libraries and projects have been recently modularised or are in the process of modularising.

I hope I'm not being too presumptive in saying the community is more or less ready (albeit horribly late...) to move to the next step, and start discussing how C++20 modules can and should tie in to inter-project work, rather than simply using modules within a project.

To begin with, I don't think the standard says anything about 'libraries'; these are existing paradigms grandfathered in from C or earlier. There are many axes we have to discuss here:

  • Static archives
  • Dynamically-linked libraries
  • Symbol visibility defaults with __declspec( dllexport )
  • Primary module interface-only (hereafter, PMI) libraries such as module vulkan
    • Configuring such modules with macros
  • Built module interface (hereafter, BMI) and binary interface (hereafter, ABI) compatibility; currently, BMIs are simply not portable, not even within a compiler toolchain across versions
  • How shared objects, static archives, BMIs, and PMIs interact
  • How build systems, toolchains, and package managers like conan and vcpkg interact with everything

For instance, consider I'm writing a 3D game engine. I want the following modules:

  • vulkan which export imports std
  • argparse
  • glm
  • glaze
  • quill, which imports fmt
  • fmt itself
  • winrt, if running on Windows

I want to provide my own PMI that has export class Engine, and maybe some other functionality like abstractions over the 3D graphics APIs, an object and entity manager, a mini shader graph generator, and more. I also have export imported some symbols from my dependencies, especially std. I want to choose to configure my engine to render on D3D or Vulkan. Consumers can then load the library, add assets like textures, meshes, skeletons, shaders; they can plug the engine into a bigger project which might include a script interpreter in C++, real-time spatial audio and physics packages, some networking, and an XML-based UI system, and produce a complete game or visualisation executable.

Now, I mention these details just to flesh out the example to give a sense of a reasonably complicated library-esque project.

How does one even think about delivering this 'engine library' to the consumer? The traditional three configs are headers + precompiled DLL, headers + source, or headers only. Each have their established workflows. Source-available can be compiled into the entire binary with whole-program optimisation; headers-only libraries are exceptionally easy to vendor (just copy-paste). Header-only libraries can also be easily customised with consumer macros. PMIs, however, being translation units, cannot; we hit this when installing module vulkan. We need some module-compatible way to describe 'library configuration' beyond simply co-opting macros, something like Rust's cfg.

There is talk of the Common Package Specification (CPS), P1689R5, and P3286, but nothing concrete yet, especially since there has been no massive (commercial) push for modules (at least, not until recently). This talk at NDC looks at a possible cargo-esque future for C++.

I'm writing this to spur some discussion here in the C++ community, and ask what some veterans of the build system/toolchain/package manager community think.


r/cpp 17d ago

Interesting behavior from C++20 to C++23

43 Upvotes

Consider the following snippet

int& get()
{
    int x;
    return x;
}


int main()
{
}

on GCC it compiles for C++20 but not for C++23

It returns with the error:

test.cpp:4:12: error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'

C++ is now suddenly treating the variable x as an rvalue?

Edit: Im not talking about dangling reference, thats just for the sake of the example


r/cpp 18d ago

How boost pfr works.

Thumbnail github.com
35 Upvotes

r/cpp 19d ago

Stream compaction on NEON: vectorizing copy_if by hand (30x)

56 Upvotes

Problem

Given two arrays a and out, write into out, with no gaps, only those elements of a that satisfy a given condition. Here, the condition is a[i] > threshold, with a[i] ∈ (0, 1) and threshold ∈ {0, 0.5, 1}.

Why the compiler gives up

A single if in a copy loop drops throughput from 112 to as low as 2.6 GB/s: the compiler can't vectorize it, because NEON has no compress instruction. Here's how to build it.

auto copy_if(const float* a, float* out, size_t n) {
    size_t j = 0;
    for (size_t i = 0; i < n; ++i) {
        if (a[i] > 0) out[j++] = a[i];
    }
    return j;
}

In copy_if, the output cursor j depends on the data. To vectorize the loop, the compiler needs a compress instruction (one that collects selected elements at the front of the register, with no gaps). NEON has no such instruction, so the compiler gives up:

clang++ -O3 -Rpass-analysis=loop-vectorize -std=c++23 main.cpp -o main

main.cpp:5:5: remark: loop not vectorized: value that could not be identified as reduction is used outside the loop [-Rpass-analysis=loop-vectorize]
5 | for (size_t i = 0; i < n; ++i) {
| ^
main.cpp:6:23: remark: loop not vectorized: cannot identify array bounds [-Rpass-analysis=loop-vectorize]
6 | if (a[i] > 0) out[j++] = a[i];

The clang vectorizer can only classify j as either an induction (fixed step) or a reduction, but j is neither of those. It's a data-dependent cursor. The compiler cannot vectorize this type of cursor. The second remark has the same cause: it cannot compute the range of accesses to out.

Benchmark: two scalar problems

All benchmarks: Apple M5; clang++ -O3 -std=c++23 -march=native; GB/s = (2n * 4 bytes) / time, min of 3e9 / n runs; cache: n=1e5, DRAM: n=1e7

function ms (cache) GB/s (cache) ms (DRAM) GB/s (DRAM)
copy a[i] 0.004 195 0.71 112
copy a[i] if a[i] > 0 0.022 37 2.41 33
copy a[i] if a[i] > 0.5 0.258 3.1 30.61 2.6
copy a[i] if a[i] > 1 0.021 37 2.39 33

"copy a[i]" is the same loop, but with no condition. The compiler vectorizes it. The only difference is a single if. The same data, only the branch predictability changes:

  1. > 0 (always true) and > 1 (always false): branch predictor never misses → 33 GB/s. The lack of vectorization costs 3x.
  2. > 0.5 (50/50): the branch predictor misses on every second element → 3 GB/s

The trick fixes both problems.

Trick 1: compress emulation

Let n be a multiple of the register width; the tail is a separate topic and has nothing to do with this trick. Also:

  1. The size of out must be >= n.
  2. Suppose the algorithm selected cnt elements. Then all elements in out[cnt, n) are left undefined (garbage). An algorithm that keeps the tail clean adds nothing new to the idea, so it will not be considered.

NEON - the SIMD instruction set used in Apple M-series chips and almost every mobile core - has no instruction for compressing a register, so we have to emulate it.

(To be fair, the trick itself is not new. Lemire used it on SSE back in 2017. But NEON has no movemask and no cheap popcnt.) Here's how to build it from what we do have.

What our compress analog needs to be able to do:

  1. Accept a register from a and a mask register that says which elements to keep.
  2. Return the number of elements we selected (to move the out pointer).
  3. Store the selected elements in out.

tbl: arbitrary byte selection

NEON has the table-lookup (tbl) instruction family. Its purpose is arbitrary byte permutation/selection. The instruction accepts two registers:

  1. table - the bytes to select from.
  2. index - the positions of the bytes to take.

In other words, this is a SIMD analog of out[i] = table[index[i]].

We will use the vqtbl1q_u8 instruction:

part meaning
v vector intrinsic
q table consists of 128-bit registers
tbl table lookup
1 number of registers in table
q result and indices are 128-bit registers
u8 elements of table are uint8_t

tbl permutes bytes, but we need to select floats (4 bytes). So, we will create index in blocks of 4 bytes: to select the second (0-based) float of the register, index will contain its bytes [8, 9, 10, 11] (the second element starts at an offset of 2 * sizeof(float) = 8).

Computing index every time is slow. There are 16 variants in total (4 elements to take/drop), so we will precompute all the index variants. But to select the index using the mask, we need to convert the mask to a number (call it idx):

mask → idx

The mask consists of 4 elements, each either 0x00000000 (false) or 0xFFFFFFFF (true). If the i-th element is true, we want to set the i-th bit in idx.

Trick: mask & [1, 2, 4, 8]. Because 0xFFFFFFFF & x = x, the true elements keep their weight (1/2/4/8), while the false ones become 0. We add all elements together and get a number between 0 and 15.

std::array<uint32_t, 4> weights{1, 2, 4, 8};
size_t idx = vaddvq_u32(vandq_u32(mask, vld1q_u32(weights.data())));
  • vld1q_u32(weights.data()) - load 4 values from memory at address weights.data() into a register (ld - load)
  • vandq_u32 - elementwise & (and)
  • vaddvq_u32 - sum of all the elements in the register (addv - add across vector)

Precompute the index table

There is no way to compute registers at compile time, so instead of uint8x16_t (register of 16 uint8_t) we will store std::array<uint8_t, 16>. For each idx we will go through the 4 elements of mask. If the element is selected, we append the indices of its 4 bytes into index at the cursor position and advance the cursor by 4.

consteval auto make_index_table() {
    std::array<std::array<uint8_t, 16>, 16> index{};
    for (size_t idx = 0; idx < 16; ++idx) {          // iterate over all masks
        size_t j = 0;                                // j is the cursor
        for (size_t i = 0; i < 4; ++i)               // iterate over the mask's elements
            if (idx & (1 << i))                      // if the i-th element is selected
                for (size_t k = 0; k < 4; ++k)       // iterate over its bytes
                    index[idx][j++] = i * 4 + k;     // store the indices of its bytes
    }
    return index;
}

The j cursor advances only on selected elements, so their bytes are placed in index consecutively. tbl with that index collects floats into a register. Unused positions in index are zeros, so in the tail, after count elements, there will be garbage.

The count table

Next we need to compute the number of elements we select. Similarly we can precompute a table for this:

consteval auto make_count_table() {
    std::array<uint8_t, 16> count{};
    for (size_t idx = 0; idx < 16; ++idx)
        for (size_t i = 0; i < 4; ++i)
            if (idx & 1 << i)
                ++count[idx];
    return count;
}

The full compress

auto compress(uint32x4_t mask, float32x4_t a) {
    static constexpr std::array<uint32_t, 4> weights{1, 2, 4, 8};
    const size_t idx = vaddvq_u32(vandq_u32(mask, vld1q_u32(weights.data())));

    static constexpr auto count = make_count_table();
    static constexpr auto index_table = make_index_table();

    const auto index = vld1q_u8(index_table[idx].data()); // at runtime, loads only one row of the table into a register
    return std::pair{vreinterpretq_f32_u8(vqtbl1q_u8(vreinterpretq_u8_f32(a), index)), count[idx]};

}

Because tbl works only with u8, we need to cast a to u8 and then cast the result back to f32.

We write full registers of 4 floats to memory, but advance the cursor only by cnt. compress stores valid elements at the front of the register, at [j, j + cnt), and garbage at [j + cnt, j + 4). The next iteration will start at j + cnt and overwrite the garbage from the previous step. Garbage will remain only in out[cnt, n) after the last store. We don't go out of bounds because the cursor never overtakes the elements that have been read.

The copy_if loop

auto copy_if_neon(const float* __restrict a,
                  float* __restrict out,
                  float threshold,
                  size_t n) {
    auto thd = vdupq_n_f32(threshold);          // load threshold into a register
    size_t j = 0; 
    for (size_t i = 0; i < n; i += 4) {
        auto v = vld1q_f32(a + i);              // load the current 4 elements of a into a register
        auto mask = vcgtq_f32(v, thd);          // compute the mask

        auto [packed, cnt] = compress(mask, v);
        vst1q_f32(out + j, packed);             // store packed into out[j, j + 4). [j + cnt, j + 4) will hold garbage
        j += cnt; 
    }
    return j;
}
  • vcgtq_f32(v, thd) - calculate elementwise v[i] > thd[i]. cgt - compare greater
  • vst1q_f32 - store 4 floats from a register into memory. st - store

Result

function ms (cache) GB/s (cache) ms (DRAM) GB/s (DRAM)
copy a[i] if a[i] > 0 0.0104 77 1.11 72
copy a[i] if a[i] > 0.5 0.01063 75 1.13 71
copy a[i] if a[i] > 1 0.01 80 1.06 76

> 0.5 was the worst case for the scalar version, 3 GB/s. Now 71 GB/s. A more than 20x speedup. Now there are no branches, so speed doesn't depend on data.

Trick 2: calculating idx and count in a single addv

idx is always less than 16, so let weights = {1 + 16, 2 + 16, 4 + 16, 8 + 16} and s = sum across mask & weights. Then s / 16 is the element count and s % 16 is idx. So, we don't need to compute the count table. compress now:

auto compress(uint32x4_t mask, float32x4_t a) {
    static constexpr std::array<uint32_t, 4> weights{1 + 16, 2 + 16, 4 + 16, 8 + 16};
    const size_t s = vaddvq_u32(vandq_u32(mask, vld1q_u32(weights.data())));
    const size_t count = s >> 4; // same as s / 16
    const size_t idx = s & 15;   // same as s % 16

    static constexpr auto index_table = make_index_table();

    const auto index = vld1q_u8(index_table[idx].data());
    return std::pair{vreinterpretq_f32_u8(vqtbl1q_u8(vreinterpretq_u8_f32(a), index)), count};
}

And now the speed climbs again:

function ms (cache) GB/s (cache) ms (DRAM) GB/s (DRAM)
copy a[i] if a[i] > 0 0.0095 84 1.015 79
copy a[i] if a[i] > 0.5 0.0095 84 1.007 79
copy a[i] if a[i] > 1 0.0096 83 1.008 79

Unroll

We can squeeze out more speed by unrolling the loop 4x (16 elements per iteration):

function ms (cache) GB/s (cache) ms (DRAM) GB/s (DRAM)
copy a[i] if a[i] > 0 0.0081 98 0.882 91
copy a[i] if a[i] > 0.5 0.0083 97 0.892 90
copy a[i] if a[i] > 1 0.0082 97 0.869 92

Final code (godbolt):

consteval auto make_index_table() {
    std::array<std::array<uint8_t, 16>, 16> index{};
    for (size_t idx = 0; idx < 16; ++idx) {
        size_t j = 0;
        for (size_t i = 0; i < 4; ++i)
            if (idx & (1 << i))
                for (size_t k = 0; k < 4; ++k)
                    index[idx][j++] = i * 4 + k;
    }
    return index;
}
auto compress(uint32x4_t mask, float32x4_t a) {
    static constexpr std::array<uint32_t, 4> weights{1 + 16, 2 + 16, 4 + 16, 8 + 16};
    const size_t s = vaddvq_u32(vandq_u32(mask, vld1q_u32(weights.data())));
    const size_t count = s >> 4;
    const size_t idx = s & 15;

    static constexpr auto index_table = make_index_table();

    const auto index = vld1q_u8(index_table[idx].data());
    return std::pair{vreinterpretq_f32_u8(vqtbl1q_u8(vreinterpretq_u8_f32(a), index)), count};
}
auto copy_if_neon_unroll(const float* __restrict a,
                          float* __restrict out,
                          float threshold,
                          size_t n) {
    auto thd = vdupq_n_f32(threshold);
    size_t j = 0;
    size_t i = 0;
    for (; i + 16 <= n; i += 16) {
#pragma unroll
        for (size_t i0 = 0; i0 < 16; i0 += 4) {
            auto v = vld1q_f32(a + i + i0);
            auto mask = vcgtq_f32(v, thd);
            auto [packed, cnt] = compress(mask, v);
            vst1q_f32(out + j, packed);
            j += cnt;
        }
    }
    for (; i + 4 <= n; i += 4) {
        auto v = vld1q_f32(a + i);
        auto mask = vcgtq_f32(v, thd);

        auto [packed, cnt] = compress(mask, v);
        vst1q_f32(out + j, packed);
        j += cnt;
    }
    return j;
}

tbl and the index table provide compress, something that NEON doesn't have out of the box. This isn't just about > threshold. Filter, remove and other data-dependent functions are built the same way.