Skip to content

Instantly share code, notes, and snippets.

@davidspry
Created June 4, 2025 11:44
Show Gist options
  • Select an option

  • Save davidspry/06b58281da6133e2def5a4b452736318 to your computer and use it in GitHub Desktop.

Select an option

Save davidspry/06b58281da6133e2def5a4b452736318 to your computer and use it in GitHub Desktop.
Generic reduction functions amenable to inlining
#pragma once
#include <concepts>
#include <cstddef>
#include <functional>
#include <type_traits>
#include <utility>
/// Right-fold reduce N successive invocations of the given object.
///
/// @details
/// Equivalent to (reduce(fn(), reduce(..., reduce(fn(), fn()))))
///
/// @tparam N
/// The number of invocations
///
/// @tparam Rd
/// A binary reduction function
///
/// @example
/// fold_right<N, std::plus<float>>([&] { return next_sample(); });
template<size_t N, class BinaryReduce, std::invocable Fn>
requires (N != 0) && std::is_invocable_v<BinaryReduce, std::invoke_result_t<Fn>, std::invoke_result_t<Fn>>
constexpr auto fold_right(Fn&& fn) {
return [&, reduce = BinaryReduce{}]<size_t K>(this auto&& self, auto&& operand) requires (K <= N) {
if constexpr (K == N) return std::move(operand);
if constexpr (K != N) return std::invoke(reduce, std::move(operand),
self.template operator()<K + 1>(std::invoke(fn))
);
}.template operator()<1>(std::invoke(fn));
}
/// Right-fold reduce N successive invocations of the given object,
/// instantiatings its operator() template with the loop index
/// for each invocation.
///
/// @details
/// Equivalent to (reduce(fn<0>(), reduce(..., reduce(fn<N-2>(), fn<N-1>()))))
///
/// @tparam N
/// The number of invocations
///
/// @tparam Reducer
/// A binary reduction function
///
/// @example
/// fold_right<N, std::plus<unsigned>>([]<size_t K> { return Coefficient<K>::value; });
template<size_t N, class BinaryReduce, class Fn>
requires (N != 0) &&
requires { std::declval<Fn>().template operator()<N>(); } &&
requires { std::declval<BinaryReduce>()(std::declval<Fn>().template operator()<N>(), std::declval<Fn>().template operator()<N>()); }
constexpr auto fold_right(Fn&& fn) {
return [&, reduce = BinaryReduce{}]<size_t K>(this auto&& self, auto&& operand) requires (K <= N) {
if constexpr (K == N) return std::move(operand);
if constexpr (K != N) return std::invoke(reduce, std::move(operand),
self.template operator()<K + 1>(fn.template operator()<K>())
);
}.template operator()<1>(fn.template operator()<0>());
}
/// Left-fold reduce N successive invocations of the given object.
///
/// @details
/// Equivalent to (reduce(...reduce(reduce(fn(), fn()), fn()), ..., fn()))
///
/// @tparam N
/// The number of invocations
///
/// @tparam Rd
/// A binary reduction function
///
/// @example
/// fold_left<N, std::plus<float>>([&] { return next_sample(); });
template<size_t N, class BinaryReduce, std::invocable Fn>
requires (N != 0) && std::is_invocable_v<BinaryReduce, std::invoke_result_t<Fn>, std::invoke_result_t<Fn>>
constexpr auto fold_left(Fn&& fn) {
return [&, reduce = BinaryReduce{}]<size_t K>(this auto&& self, auto&& operand) requires (K <= N) {
if constexpr (K == N) return std::move(operand);
if constexpr (K != N) return self.template operator()<K + 1>(
std::invoke(reduce, std::move(operand), std::invoke(fn))
);
}.template operator()<1>(std::invoke(fn));
}
/// Left-fold reduce N successive invocations of the given object,
/// instantiatings its operator() template with the loop index
/// for each invocation.
///
/// @details
/// Equivalent to (reduce(...reduce(reduce(fn<0>(), fn<1>()), fn<2>()), ..., fn<N-1>()))
///
/// @tparam N
/// The number of invocations
///
/// @tparam Reducer
/// A binary reduction function
///
/// @example
/// fold_left<N, std::multiplies<float>>([this]<size_t K> { return std::get<K>(m_factors); });
template<size_t N, class BinaryReduce, class Fn>
requires (N != 0) &&
requires { std::declval<Fn>().template operator()<N>(); } &&
requires { std::declval<BinaryReduce>()(std::declval<Fn>().template operator()<N>(), std::declval<Fn>().template operator()<N>()); }
constexpr auto fold_left(Fn&& fn) {
return [&, reduce = BinaryReduce{}]<size_t K>(this auto&& self, auto&& operand) requires (K <= N) {
if constexpr (K == N) return std::move(operand);
if constexpr (K != N) return self.template operator()<K + 1>(
std::invoke(reduce, std::move(operand), fn.template operator()<K>())
);
}.template operator()<1>(fn.template operator()<0>());
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment