Last active
December 28, 2023 23:26
-
-
Save XDracam/39039e600cb6cf40dfd70ec86164e591 to your computer and use it in GitHub Desktop.
Constexpr range literals with optional type inference
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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