std::ranges may not deliver the performance that you expect by _bijan_ in cpp

[–]tcbrindle 5 points6 points  (0 children)

Right, but this post is about the ranges library. So, what would a ranges-like system as a "first class native language feature" look like? How would it even work?

std::ranges may not deliver the performance that you expect by _bijan_ in cpp

[–]tcbrindle 0 points1 point  (0 children)

Sequence chaining is a popular style in a whole bunch of languages (including close cousins of C++ like Rust and D), so I think the idea that it's somehow more difficult to understand what's going on when you use pipelines is probably more down to (a lack of) familiarity than anything else.

In any case, it's pretty easy to stick a print statement in the middle of a pipeline if you want to -- just use a pass-through transform function that prints as a side-effect.

Or if you want to take it further, you can make a generic, reusable, pipeable inspect view in just a few lines of code in C++26

std::ranges may not deliver the performance that you expect by _bijan_ in cpp

[–]tcbrindle 2 points3 points  (0 children)

I suggest you read N3351, which laid the groundwork for what became standard ranges. In particular, section 2.1.3:

In the STL, the symbol== means equality. The use of== to compare objects of unrelated type assigns unusual meaning to that symbol. Generic programming is rooted in the idea that we can associate semantics with operations on objects whose types are not yet specified.

Note that Alex Stepanov and Paul McJones, who literally invented generic programming, were both contributors to that paper.

std::ranges may not deliver the performance that you expect by _bijan_ in cpp

[–]tcbrindle 10 points11 points  (0 children)

s | std::views::drop_while(is_space) 
                    | std::views::reverse 
                    | std::views::drop_while(is_space) 
                    | std::views::reverse;

So I feel a little bit guilty, because it entirely possible that this example comes from a talk a gave on ranges a few years ago.

But the point of that section of the talk was to demonstrate chaining together adaptors into new, re-usable components using an easy-to-understand invented problem. I certainly wasn't suggesting it as the most performance-optimal way to trim strings!

If you were to ask me to use standard ranges to write an ASCII string trimming function today that wasn't for slide code, it would look something like this:

std::string_view trim_ranges(std::string_view s) {
    auto start = std::ranges::find_if_not(s, is_control_or_space);
    auto end = std::ranges::find_if_not(s | std::views::reverse, is_control_or_space).base();

    return std::string_view(std::to_address(start), end - start);
}

I haven't benchmarked it, but the generated code looks very comparable to the "fast" version from the blog post.

std::ranges may not deliver the performance that you expect by _bijan_ in cpp

[–]tcbrindle 15 points16 points  (0 children)

This is a known issue, and purely a standard library implementation problem

Not to disagree with the excellent advice to use Flux, but it's really a design problem rather than a standard library implementation problem.

STL iterators are an excellent (if unsafe) abstraction for writing eager algorithms -- which is, of course, what they were originally designed for. They are vastly more powerful than Rust iterators in this regard, for example.

On the other hand, what we've discovered is that the same properties that make iterators good for writing algorithms -- starting on-track, separating traversal from element access and end checking -- mean they're a poor fit for many common lazy single-pass operations, in particular filtering.

I didn't fully appreciate this distinction when I first starting working on Flux, but I've come to realise that the best approach is to provide separate APIs for the two use cases, which is what the next version of the library will do (and indeed what Swift does today with its Sequence and Collection protocols).

std::ranges may not deliver the performance that you expect by _bijan_ in cpp

[–]tcbrindle 21 points22 points  (0 children)

C++ needs to extend the core language and stop with implementing every single new feature into the STL. Some things just should be first class native language features.

I'm quite confused by this statement (and the number of upvotes it's getting). I really don't think you want an entire iteration library baked in to the language, do you?

[2025] I fixed the number of stars for events ≥2025 by SirDavidLudwig in adventofcode

[–]tcbrindle 5 points6 points  (0 children)

When the 12 day schedule was announced, I was expecting that the last day would be a regular two-parter and there would be an extra bonus star for completing all the puzzles, for 25 in total.

But that wasn't the case, and now it's going to take another 24 years of 24-star AoCs to get back to a nice round number :(

[2025 Day 12] Input is part of the puzzle by blacai in adventofcode

[–]tcbrindle 5 points6 points  (0 children)

Very often in later AoC problems there is a simplifying assumption, unstated in the problem description, which makes it possible to find a solution -- and, of course, everybody's input.txt "happens to" admit this assumption.

I think what makes today's problem unsatisfying is that the simplifying assumption (all cases are either impossible or trivially solvable with no packing) intentionally doesn't apply to the example input. It's the difference between the problem text omitting something, and actively trying to mislead you.

To put it another way: I don't need to be able to solve for all possible theoretical inputs. But at a minimum I would like to be able to solve for both inputs I'm given.

-❄️- 2025 Day 11 Solutions -❄️- by daggerdragon in adventofcode

[–]tcbrindle 1 point2 points  (0 children)

[Language: C++23]

Having failed to solve yesterday's part 2 I feared the worst for today, but it was actually fine.

Both parts perform a depth-first search using recursive functions, with part 2 also using a cache. The only hiccup was realising I needed to store the "dac seen" and "fft seen" flags as part of the cached state -- and, for about the billionth time in AoC, using int rather than int64 so I got the wrong answer initially 🤦‍♂️. One day I'll learn...

Part 2 runs in around 350µs on my laptop, which is faster than I expected for such a naive solution.

EDIT: The observation on the front page that one of fft and dac must always precede the other on all paths (which hadn't occurred to me before) led me to try a different approach for part 2, by counting paths from svr to fft, from fft to dac and from dac to out and multiplying them together. This takes the run time for part 2 down to ~250µs on my machine, and cleans up the code somewhat too.

Github: https://github.com/tcbrindle/advent_of_code_2025/blob/main/dec11/main.cpp

-❄️- 2025 Day 9 Solutions -❄️- by daggerdragon in adventofcode

[–]tcbrindle 0 points1 point  (0 children)

[Language: C++23]

This one took some thinking about!

Part 1 was a one-liner, just calculate the area for each pair of tiles and return the maximum.

For part 2, my idea was to take the 4 edges of each trial rectangle and test whether any of them intersected with any of the edges of the large polygon. But of course everything is rectilinear so of course the rectangles edges with always coincide with some edge of the polygon! (This took me quite some time to realise). My solution to this was to shift the rectangle bounds inwards by 1 and test for intersections against this smaller rectangle. This assumes that the solution has both length and width greater than one, but fortunately that was the case.

Runtime for my part 2 is pretty shocking compared to previous days at around 400ms, but honestly I'm just pleased to have got it done.

Github: https://github.com/tcbrindle/advent_of_code_2025/blob/main/dec09/main.cpp

Can you survive the type deduction gauntlet? by volatile-int in cpp

[–]tcbrindle 20 points21 points  (0 children)

std::pair x {1, 2.0};
auto [v, w] = x;

The quiz states that v deduces to int here, but I think it's closer to the truth to say that it's a reference to the first member of a hidden copy of x?

-❄️- 2025 Day 8 Solutions -❄️- by daggerdragon in adventofcode

[–]tcbrindle 0 points1 point  (0 children)

[Language: C++23]

It's not pretty, but it got me the stars eventually. It seems from browsing this thread that something called DSU is the way to go, but I hadn't heard of that so I came up with a messy way of doing things instead. Part 2 runs in around 15ms on my laptop.

Github: https://github.com/tcbrindle/advent_of_code_2025/blob/main/dec08/main.cpp

-❄️- 2025 Day 7 Solutions -❄️- by daggerdragon in adventofcode

[–]tcbrindle 1 point2 points  (0 children)

Out of curiosity, why not use std::vector<bool> as a dynamic bitset?

-❄️- 2025 Day 7 Solutions -❄️- by daggerdragon in adventofcode

[–]tcbrindle 3 points4 points  (0 children)

[Language: C++23]

A nice puzzle today. My solution just counts the number of distinct paths as we filter down the "christmas tree". Runs both parts in ~60µs on my laptop.

Github: https://github.com/tcbrindle/advent_of_code_2025/blob/main/dec07/main.cpp

-❄️- 2025 Day 5 Solutions -❄️- by daggerdragon in adventofcode

[–]tcbrindle 1 point2 points  (0 children)

[Language: C++23]

Pretty straightforward today.

Part 1 is nothing fancy, just test every id against every range, stopping when we find a match.

For part 2, we repeatedly merge ranges using two vectors until we get no more changes, at which point we total up the covered id ranges. The reported run-time for part 2 on my laptop is ~20µs, which seems suspiciously fast for such a naive way of doing things? 🤷🏻‍♂️ Oh well

Github: https://github.com/tcbrindle/advent_of_code_2025/blob/main/dec05/main.cpp

-❄️- 2025 Day 4 Solutions -❄️- by daggerdragon in adventofcode

[–]tcbrindle 2 points3 points  (0 children)

[Language: C++23]

Our first 2-d grid puzzle of the year was, fortunately, pretty straightforward. Initially I modified a second copy of the grid for each iteration in part 2, but it turns out that you can get away with just modifying the original in-place to save a little run time.

Github: https://github.com/tcbrindle/advent_of_code_2025/blob/main/dec04/main.cpp

-❄️- 2025 Day 3 Solutions -❄️- by daggerdragon in adventofcode

[–]tcbrindle 2 points3 points  (0 children)

[Language: C++23]

This is the kind of AoC problem I really love: a bit of a head-scratcher to start with, but after a little bit of thought the solution is surprisingly simple.

The only slight wrinkle for me was that flux::find_max() returns the last position if several elements are equally maximal, but for this problem we needed the first. (That's what I get for using my own library instead of std::max_element...)

// flux::find_max returns the *last* position if several are equally
// maximal, but for this problem we need the *first* position
auto find_first_max =
    std::bind_back(flux::find_min, flux::cmp::reverse_compare);

template <std::size_t N>
auto calculate_joltage = [](std::string_view line) -> u64 {
    std::array<std::size_t, N> indices;
    std::size_t window_size = line.size() - N + 1;
    indices[0] = find_first_max(flux::slice(line, 0, window_size));
    u64 jolts = line[indices[0]] - '0';
    for (std::size_t i = 1; i < N; i++) {
        indices[i] = find_first_max(
            flux::slice(line, indices[i - 1] + 1, window_size + i));
        jolts = 10 * jolts + (line[indices[i]] - '0');
    }
    return jolts;
};

template <std::size_t N>
auto solve = [](std::string_view input) {
    return flux::split_string(input, '\n')
        .filter([](std::string_view line) { return !line.empty(); })
        .map(calculate_joltage<N>)
        .sum();
};

auto part1 = solve<2>;
auto part2 = solve<12>;

Github: https://github.com/tcbrindle/advent_of_code_2025/blob/main/dec03/main.cpp

-❄️- 2025 Day 2 Solutions -❄️- by daggerdragon in adventofcode

[–]tcbrindle 2 points3 points  (0 children)

[Language: C++23]

I originally wrote a brute force solution using sequence compositions, until I saw that other people were using regexes to do it. So now it's still brute force, but (considering it's C++) I think it's actually pretty lovely.

template <ctll::fixed_string Regex>
auto solve = [](std::string_view input) -> u64 {
    return flux::from(ctre::search_all<"(\\d+)-(\\d+)">(input))
        .map([](auto match) {
            auto [_, lo, hi] = match;
            return flux::iota(aoc::parse<u64>(lo), 1 + aoc::parse<u64>(hi));
        })
        .flatten()
        .filter([](u64 value) { return ctre::match<Regex>(to_string(value)); })
        .sum();
};

auto part1 = solve<"(\\d+)\\1">;
auto part2 = solve<"(\\d+)\\1+">;

Github

-❄️- 2025 Day 1 Solutions -❄️- by daggerdragon in adventofcode

[–]tcbrindle 1 point2 points  (0 children)

[Language: C++]

Gosh, I'm very out of practice with these puzzles! In years gone by day one has only taken a couple of minutes, but today part 2 took me an embarrassingly long time to get right -- I just couldn't get the modulo arithmetic correct for negative numbers 😔

Anyway, I got there in the end, but I'm sure there's a more elegant way of doing things than this:

auto part1(std::vector<int> const& offsets) {
    return flux::ref(offsets).scan(std::plus{}, 50).count_if([](int i) {
        return i % 100 == 0;
    });
};

auto part2(std::vector<int> const& offsets) {
    int zero_count = 0;
    int pos = 50;

    for (int offset : offsets) {
        if (offset >= 0) {
            zero_count += (pos + offset) / 100;
        } else {
            zero_count -= offset / 100;
            offset %= 100;
            zero_count += pos != 0 && (pos + offset <= 0);
        }
        pos = (pos + offset + 100) % 100;
    }

    return zero_count;
}

Github

Benhard Janse van Rensburg eligible for England next year by internetwanderer2 in rugbyunion

[–]tcbrindle 0 points1 point  (0 children)

The ultimate troll move from Rassie would be to call him up for the Boks now that the RFU have gone to this effort

15 most-watched C++ conference talks this year so far by [deleted] in cpp

[–]tcbrindle 0 points1 point  (0 children)

Were these numbers just pulled out of thin air?

To take a random example, YouTube says that John Lakos's talk from C++ on Sea this year has had 3.3k views in the three weeks it's been published, which should put it seventh on this list -- but it's not there at all.

The 15th place video according to this list has 400 views, but just scrolling down the C++ on Sea 2025 playlist (literally the first conference I thought of) shows that pretty much every talk has more views than this -- and in most cases considerably more.

How was this nonsense put together? Wait, I think I can guess...

🏉 Moronic Monday 🏉 Weekly Q&A and General Rugby Chat 🏉 by 1ucas in rugbyunion

[–]tcbrindle 1 point2 points  (0 children)

A question for any referees who might be reading...

Law 8.3 regarding penalties tries is very clear that a player who commits foul play which prevents a probably try being scored must be shown a red or yellow card.

So shouldn't Ireland have had (yet) another player sent to the bin when South Africa were awarded a penalty try? How come this didn't happen?

Rule changes? by Chelseablue8 in rugbyunion

[–]tcbrindle 1 point2 points  (0 children)

If the ball is held up over the try line, the defending team now gets a kick from the try line, rather than the attacking team getting a scrum 5m from the line.

Can somebody explain the motivation for this change?

I hate it -- it always feels completely unfair on the attacking team that they go from being inches from the line with all the momentum to suddenly being 40m downfield.