Skip to content

Instantly share code, notes, and snippets.

@XDracam
Last active December 28, 2023 23:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save XDracam/39039e600cb6cf40dfd70ec86164e591 to your computer and use it in GitHub Desktop.
Save XDracam/39039e600cb6cf40dfd70ec86164e591 to your computer and use it in GitHub Desktop.
Constexpr range literals with optional type inference
// Requires C++17. Tested on Clang 9.0.0, but should be portable.
// Modern C++ code uses ranges for more expressive code, compared to begin- and end-iterators.
// When writing code that operators on ranges, you often end up with code like this (when not using concepts):
//
// template <typename TRange>
// auto foo(const TRange& range);
//
// So how do you call this with a fixed sequence of elements? You could pass an `std::vector`,
// but that incurs one heap allocation (potentially two, if using an `std::initializer_list`).
// Even more annoying, you have to explicitly specify the type of the vector contents.
//
// `std::array` is usually stack-allocated and provides constexpr iterators and other goodies.
// However, constructing one using an initializer list requires an additional heap allocation.
//
// This implementation provides overloads to either explicitly specify the sequence's type
// or to derive the most common type automagically from the provided arguments.
//
// All arguments are forwarded using perfect forwarding, thus enabling a mix of lvalue,
// rvalue and const references as arguments, as long as they share a common type or are
// implicitly convertible to one distinct type.
//
// As far as I have tested, this yields exactly the same assembly as constructing an `std::array`
// through an initializer list on -O3, but this code is more flexible in some edge cases
// involving constexpr type conversions.
//
// As a downside, using this code might cause compile times to increase, as an `array<int, 2>`
// is a different type than `array<int, 3>`, thereby causing all templated methods to be
// recompiled for a sequence of each used distinct size.
#include <utility>
#include <array>
namespace utils {
namespace detail {
template <std::size_t index, typename T, typename ArrT>
constexpr void set_array_at(ArrT& array, T&& value) { array[index] = std::forward<T>(value); }
template <typename ArrT, typename... Ts, std::size_t... indices>
constexpr void fill_array(
ArrT& array, std::index_sequence<indices...>, Ts&&... values
) { (set_array_at<indices>(array, std::forward<Ts>(values)), ...); }
}
/** Wraps a single object into a constexpr stack-allocated range. **/
template <typename T>
inline constexpr std::array<T, 1> seq(T&& single) {
std::array<std::decay_t<T>, 1> res {};
res[0] = std::forward<T>(single);
return res;
}
/** Constructs a constexpr stack-allocated range of the specified type: `seq<double>(1, 2, 3)`. **/
template <typename TRes, typename... Ts, typename = std::enable_if_t<(std::is_convertible_v<Ts, TRes> and ...)>>
inline constexpr auto seq(Ts&&... values) -> std::array<TRes, sizeof...(Ts)> {
std::array<TRes, sizeof...(Ts)> res {};
detail::fill_array(res, std::make_index_sequence<sizeof...(Ts)>{}, std::forward<Ts>(values)...);
return res;
}
/** Constructs a constexpr stack-allocated range of the common type of all arguments. **/
template <typename T, typename... Ts,
typename CommonT = typename std::common_type_t<T, typename std::common_type_t<std::decay_t<Ts>...>>>
inline constexpr auto seq(T&& first, Ts&&... rest) -> std::array<CommonT, 1 + sizeof...(Ts)> {
return seq<CommonT>(std::forward<T>(first), std::forward<Ts>(rest)...);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment