Sorting at compile time, part 2
Yesterday, in “Sorting integer_sequence
at compile time” (2024-02-19),
I wrote:
I don’t know any good way to operate on a whole pack of
Is...
in value-space and then “return” them into type-space asVector<Is...>
.
So I had written a helper function to get each individual element of the result one by one, like this:
template<class> struct Sort;
template<int... Is> struct Sort<Vector<Is...>> {
static constexpr int GetNthElement(int i) {
int arr[] = {Is...};
std::ranges::sort(arr);
return arr[i];
}
template<class> struct Helper;
template<int... Indices> struct Helper<Vector<Indices...>> {
using type = Vector<GetNthElement(Indices)...>;
};
using type = Helper<Iota<sizeof...(Is)>>::type;
};
Alert reader “Chlorie” writes in to tell me the proper approach. We just need our helper to return
a std::array
of all the elements at once, and then — still — use a pack-expansion to unpack
the elements out of that constexpr std::array
and into Vector
’s template argument list.
(Here we use std::array
, not int[]
, despite my usual advice,
because we need something that can be returned from a function.) Godbolt:
template<int N> struct Array { int data[N]; };
template<class> struct Sort;
template<int... Is> struct Sort<Vector<Is...>> {
static constexpr auto sorted = []() {
Array<sizeof...(Is)> arr = {Is...};
std::ranges::sort(arr.data);
return arr;
}();
template<class> struct Helper;
template<int... Indices> struct Helper<Vector<Indices...>> {
using type = Vector<sorted.data[Indices]...>;
};
using type = Helper<Iota<sizeof...(Is)>>::type;
};
Now that sort
is called only once, we have a truly \(O(n\log n)\) compile-time sorting operation.
Updated compile-time benchmark
Yesterday’s benchmark is still here;
but I’ve updated it
to include Chlorie’s proper \(O(n\log n)\) constexpr solution, using both std::sort
and std::ranges::sort
.
Here’s Clang trunk (the future Clang 19) with libc++, running on my MacBook.
The new solutions are the orange and red lines closest to the X-axis. The orange and red
lines higher up (unchanged from yesterday’s graph) are yesterday’s std::sort
and std::ranges::sort
solutions, which do basically \(n\) times as much work as today’s solutions.
Here’s GCC 12.2 with libstdc++, running on RHEL 7.1.
There’s still a noticeable compile-time-perf difference between libstdc++’s sort
and ranges::sort
,
but even ranges::sort
(in orange) now wins handily against yesterday’s recursive-template selection sort (in black).