Skip to content

Instantly share code, notes, and snippets.

@Atari2
Last active May 18, 2022 11:18
Show Gist options
  • Save Atari2/5bc5f83670b1fab7baf8c5db9172c52f to your computer and use it in GitHub Desktop.
Save Atari2/5bc5f83670b1fab7baf8c5db9172c52f to your computer and use it in GitHub Desktop.
Common utilities in a header file.
#pragma once
#include <array>
#include <bit>
#include <concepts>
#include <cstdint>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#ifdef BACKTRACE_AVAILABLE
#include "BackTrace.h"
#endif
// common and useful macros / functions
#define STRIMPL(x) #x
#define STR(x) STRIMPL(x)
// defines a literal with the format "DEBUG [file:line]: <info>"
#define DEBUGINFO(info) "DEBUG [" __FILE__ ":" STR(__LINE__) "]: " STR(info)
#define WIDEIMPL(str) L##str
#define WIDE(str) WIDEIMPL(str)
// A catch-all tag type.
template <typename T>
struct TagType {};
// Concept for enum template parameters
template <typename T>
concept EnumT = std::is_enum_v<T>;
template <typename T, size_t N>
constexpr size_t sizeof_array(const T (&)[N]) {
return N;
}
// Simple choice support
template <typename T>
constexpr auto choice(bool t, T ret_true, T ret_false) {
return (t ? ret_true : ret_false);
}
template <typename T>
constexpr auto triple_choice(bool a, bool b, T reta, T retb, T retc) {
return (a ? reta : (b ? retb : retc));
}
// enum conversions
template <EnumT T>
constexpr auto from_enum(T val) {
return static_cast<std::underlying_type_t<T>>(val);
}
template <EnumT T>
constexpr auto to_enum(std::integral auto val) {
return static_cast<T>(static_cast<std::underlying_type_t<T>>(val));
}
// an always_false template dependant bool for static_asserts in if constexpr code
template <typename T>
constexpr inline bool always_false_v = false;
// size explicit integer shorthands and operators
using u8 = uint8_t;
using i8 = int8_t;
using u16 = uint16_t;
using i16 = int16_t;
using u32 = uint32_t;
using i32 = int32_t;
using u64 = uint64_t;
using i64 = int64_t;
using c = char;
using uc = unsigned char;
constexpr u8 operator""_u8(unsigned long long val) {
return static_cast<u8>(val);
}
constexpr u16 operator""_u16(unsigned long long val) {
return static_cast<u16>(val);
}
constexpr u32 operator""_u32(unsigned long long val) {
return static_cast<u32>(val);
}
constexpr u64 operator""_u64(unsigned long long val) {
return static_cast<u64>(val);
}
constexpr i8 operator""_i8(unsigned long long val) {
return static_cast<i8>(val);
}
constexpr i16 operator""_i16(unsigned long long val) {
return static_cast<i16>(val);
}
constexpr i32 operator""_i32(unsigned long long val) {
return static_cast<i32>(val);
}
constexpr i64 operator""_i64(unsigned long long val) {
return static_cast<i64>(val);
}
constexpr size_t operator""_sz(unsigned long long val) {
return static_cast<size_t>(val);
}
constexpr intptr_t operator""_iptr(unsigned long long val) {
return static_cast<intptr_t>(val);
}
constexpr uintptr_t operator""_uptr(unsigned long long val) {
return static_cast<uintptr_t>(val);
}
constexpr c operator""_c(char val) {
return static_cast<c>(val);
}
constexpr uc operator""_uc(char val) {
return static_cast<uc>(val);
}
// platform information
enum class PlatformType { Windows = 0, Apple = 1, Unix = 2 };
enum class PlatformWidth { x64 = 0, x86 = 1 };
namespace __internal {
template <PlatformType TType, PlatformWidth TWidth>
struct PlatformInfoImpl {
static_assert(
TType == PlatformType::Windows || TType == PlatformType::Apple || TType == PlatformType::Unix,
"Invalid platform type"
);
static_assert(TWidth == PlatformWidth::x64 || TWidth == PlatformWidth::x86, "Invalid platform width");
constexpr static PlatformType Type = TType;
constexpr static bool WINDOWS = (TType == PlatformType::Windows);
constexpr static bool UNIX = (TType == PlatformType::Unix);
constexpr static bool APPLE = (TType == PlatformType::Apple);
constexpr static PlatformWidth Width = TWidth;
constexpr static bool X64 = (TWidth == PlatformWidth::x64);
constexpr static bool X86 = (TWidth == PlatformWidth::x86);
constexpr static const char* Name = triple_choice(WINDOWS, UNIX, "Windows", "Unix", "Apple");
constexpr static const char* StrWidth = choice(X64, "x64", "x86");
constexpr static auto type() { return Type; }
constexpr static bool is_windows() { return WINDOWS; }
constexpr static bool is_unix() { return UNIX; }
constexpr static bool is_apple() { return APPLE; }
template <typename T = PlatformWidth>
constexpr static auto width() {
if constexpr (std::same_as<T, PlatformWidth>) {
return Width;
} else if constexpr (std::same_as<T, char*>) {
return StrWidth;
} else {
static_assert(
always_false_v<T>, "width() call is only valid with PlatformWidth or char* as template parameters"
);
return nullptr;
}
}
constexpr static bool x64() { return X64; }
constexpr static bool x86() { return X86; }
constexpr static auto name() { return Name; }
};
} // namespace __internal
#ifdef _WIN32
// Windows
#ifdef _WIN64
using PlatformInfo = __internal::PlatformInfoImpl<PlatformType::Windows, PlatformWidth::x64>;
#else
using PlatformInfo = __internal::PlatformInfoImpl<PlatformType::Windows, PlatformWidth::x86>;
#endif
#define unreachable __assume(0)
#define forceinline __forceinline
#define noop __noop
#define NEWLINE "\r\n"
#elif __APPLE__
// Apple
#if defined(__LP64__) || defined(__x86_64__)
using PlatformInfo = __internal::PlatformInfoImpl<PlatformType::Apple, PlatformWidth::x64>;
#else
using PlatformInfo = __internal::PlatformInfoImpl<PlatformType::Apple, PlatformWidth::x86>;
#endif
#define unreachable __builtin_unreachable();
#define forceinline __attribute__((always_inline))
#define noop ((void)0)
#define NEWLINE "\n"
#elif __unix__
// Unix
#if defined(__LP64__) || defined(__x86_64__)
using PlatformInfo = __internal::PlatformInfoImpl<PlatformType::Unix, PlatformWidth::x64>;
#else
using PlatformInfo = __internal::PlatformInfoImpl<PlatformType::Unix, PlatformWidth::x86>;
#endif
#define unreachable __builtin_unreachable();
#define forceinline __attribute__((always_inline))
#define noop ((void)0)
#define NEWLINE "\n"
#else
#error "Unknown platform"
#endif
/* Constexpr support for various utilities */
namespace cxpr {
constexpr size_t strlen(const char* ptr) {
if (!ptr) return 0;
size_t len = 0;
while (*(ptr++)) len++;
return len;
}
constexpr bool isspace(const unsigned char c) {
constexpr const std::array whitespaces{ '\t'_uc, '\n'_uc, '\v'_uc, '\f'_uc, '\r'_uc };
constexpr unsigned char begin_ws_range = whitespaces.front();
constexpr unsigned char end_ws_range = whitespaces.back();
return c == ' ' || (c >= begin_ws_range && c <= end_ws_range);
}
constexpr bool strncmp(const char* lhs, const char* rhs, size_t size) {
for (size_t i = 0; i < size; i++) {
if (lhs[i] != rhs[i]) return false;
}
return true;
}
constexpr size_t min(size_t lhs, size_t rhs) {
return (lhs < rhs) ? lhs : rhs;
}
// minimal string_view constexpr implementation
// this exists only because std::string_view is constexpr-capable only on msvc
// (as of 20 april 2022)
class string_view {
const char* m_ptr = nullptr;
size_t m_size = 0;
public:
constexpr static size_t npos = static_cast<size_t>(-1);
constexpr string_view(const char* ptr, size_t size) : m_ptr(ptr), m_size(size) {}
constexpr string_view(const char* ptr) : m_ptr(ptr), m_size(strlen(ptr)) {}
constexpr string_view() = default;
constexpr const char* data() const { return m_ptr; }
constexpr size_t size() const { return m_size; }
constexpr size_t length() const { return m_size; }
constexpr char operator[](size_t index) const { return m_ptr[index]; }
constexpr bool operator==(const char* ptr) const {
if (strlen(ptr) == m_size) return strncmp(ptr, m_ptr, m_size);
return false;
}
constexpr bool operator==(const string_view other) const {
if (other.size() == m_size) return strncmp(other.data(), m_ptr, m_size);
return false;
}
constexpr bool operator!=(const char* ptr) const { return !(*this == ptr); }
constexpr bool operator!=(const string_view other) const { return !(*this == other); }
constexpr const char* begin() const { return m_ptr; }
constexpr const char* end() const { return m_ptr + m_size; }
constexpr string_view substr(size_t pos = 0, size_t count = npos) const {
const size_t rcount = min(count, size() - pos);
return string_view{ m_ptr + pos, rcount };
}
constexpr bool empty() const { return m_size == 0; }
constexpr size_t find_first_of(char c, size_t off = 0) const {
if (off > m_size) return npos;
for (size_t i = off; i < m_size; i++) {
if (m_ptr[i] == c) return i;
}
return npos;
}
constexpr size_t find_first_not_of(char c, size_t off = 0) const {
if (off > m_size) return npos;
for (size_t i = off; i < m_size; i++) {
if (m_ptr[i] != c) return i;
}
return npos;
}
explicit operator std::string() const { return std::string{ m_ptr, m_size }; }
constexpr operator std::string_view() const { return std::string_view{ m_ptr, m_size }; }
friend std::ostream& operator<<(std::ostream& stream, const string_view view) {
return stream << std::string_view{ view.data(), view.size() };
}
};
} // namespace cxpr
#if _MSC_VER
using _string_view = std::string_view;
#else
using _string_view = cxpr::string_view;
#endif
// constexpr string->integer conversion
namespace __internal {
template <bool Signed = true>
constexpr auto StrViewTo64Trimmed(const _string_view str) {
constexpr std::array values_table_i64 = { 1_i64,
10_i64,
100_i64,
1000_i64,
10000_i64,
100000_i64,
1000000_i64,
10000000_i64,
100000000_i64,
1000000000_i64,
10000000000_i64,
100000000000_i64,
1000000000000_i64,
10000000000000_i64,
100000000000000_i64,
1000000000000000_i64,
10000000000000000_i64,
100000000000000000_i64,
1000000000000000000_i64 };
constexpr std::array value_table_u64 = { 1_u64,
10_u64,
100_u64,
1000_u64,
10000_u64,
100000_u64,
1000000_u64,
10000000_u64,
100000000_u64,
1000000000_u64,
10000000000_u64,
100000000000_u64,
1000000000000_u64,
10000000000000_u64,
100000000000000_u64,
1000000000000000_u64,
10000000000000000_u64,
100000000000000000_u64,
1000000000000000000_u64,
10000000000000000000_u64 };
using RetType = std::conditional_t<Signed, int64_t, uint64_t>;
size_t start_idx = 0;
size_t end_idx = str.size();
bool neg = false;
if constexpr (Signed) {
char maybe_sign = str[0];
neg = maybe_sign == '-';
start_idx += (maybe_sign == '-' || maybe_sign == '+');
}
RetType result = 0;
// skip leading zeros
while (start_idx < end_idx) {
if (str[start_idx] != '0') break;
start_idx++;
}
if (start_idx == end_idx) return RetType{ 0 };
constexpr size_t max_uint64_size = cxpr::strlen("18446744073709551615");
constexpr size_t max_int64_size = cxpr::strlen("9223372036854775807");
constexpr auto max_size = Signed ? max_int64_size : max_uint64_size;
if (end_idx - start_idx > max_size) return RetType{ 0 };
constexpr auto get_values_table = [values_table_i64, value_table_u64]() {
if constexpr (Signed)
return values_table_i64;
else
return value_table_u64;
};
constexpr const auto values_table = get_values_table();
// 0-9 => 48-57
for (size_t idx = end_idx - 1, tbl_idx = 0; idx >= start_idx; idx--, tbl_idx++) {
result += static_cast<RetType>(str[idx] - '0') * values_table[tbl_idx];
if (idx == 0) break;
}
if constexpr (Signed)
return result * (neg ? -1 : 1);
else
return result;
}
} // namespace __internal
template <bool Signed>
constexpr int64_t StrViewTo64(const _string_view view) {
size_t cur_index = 0;
size_t max_index = view.length();
// skip leading and trailing whitespace
if (max_index == 0) return 0;
while (cxpr::isspace(view[cur_index])) {
cur_index++;
if (cur_index == max_index) return 0;
}
while (cxpr::isspace(view[max_index - 1])) {
max_index--;
if (max_index == _string_view::npos) return 0;
}
if (cur_index == max_index) return 0;
return __internal::StrViewTo64Trimmed<Signed>(view.substr(cur_index, max_index));
}
constexpr inline auto StrViewToU64 = StrViewTo64<false>;
constexpr inline auto StrViewToI64 = StrViewTo64<true>;
namespace EnumHelpers {
template <EnumT V, size_t N>
class EnumStrHashMap {
// round up to power of 2 to avoid modulos and just use bitwise ops
constexpr static size_t ACTUAL_SIZE = std::bit_ceil(N);
constexpr static size_t FOR_MOD_OPS = ACTUAL_SIZE - 1;
struct KeyBkt {
V key;
bool used;
};
std::array<std::pair<KeyBkt, _string_view>, ACTUAL_SIZE> m_internal_map;
size_t m_size = 0;
public:
constexpr void insert(V key, _string_view val) {
if (m_size == m_internal_map.size()) throw std::runtime_error("This map is full");
size_t idx = from_enum(key) & FOR_MOD_OPS;
while (m_internal_map[idx].first.used) { idx = (idx + 1) & FOR_MOD_OPS; }
m_internal_map[idx] = std::make_pair(KeyBkt{ std::move(key), true }, val);
m_size++;
}
constexpr _string_view operator[](V key) const {
size_t idx = from_enum(key) & FOR_MOD_OPS;
size_t n_tries = 0;
while (m_internal_map[idx].first.key != key) {
idx = (idx + 1) & FOR_MOD_OPS;
if (n_tries++ == m_size) throw std::runtime_error("This enum value is not valid");
}
return m_internal_map[idx].second;
}
};
constexpr size_t count_enum_values(_string_view view) {
size_t count = 1;
for (char c : view) count += (c == ',');
return count;
}
template <EnumT T>
constexpr _string_view get_enum_full_string(TagType<T>) {
return "";
}
template <EnumT T>
requires(!get_enum_full_string<T>({}).empty()) constexpr auto construct_enum_array(TagType<T>) {
constexpr _string_view view = get_enum_full_string<T>({});
std::array<std::pair<_string_view, T>, count_enum_values(view)> enum_map_l;
std::underlying_type_t<T> current_val{};
auto get_pair = [&view, &current_val](size_t first_idx, size_t second_idx) {
constexpr bool Signed = std::is_signed_v<std::underlying_type_t<T>>;
auto sub = view.substr(first_idx, second_idx - first_idx);
auto equal_idx = sub.find_first_of('=');
if (equal_idx == std::string_view::npos) {
// no equal, get next value
auto first_nospace_idx = sub.find_first_not_of(' ');
auto last_nospace_idx = sub.find_first_of(' ', first_nospace_idx);
return std::make_pair(sub.substr(first_nospace_idx, last_nospace_idx), to_enum<T>(current_val++));
} else {
auto first_nospace_idx = sub.find_first_not_of(' ');
auto last_nospace_idx = sub.find_first_of(' ', first_nospace_idx);
return std::make_pair(
sub.substr(
first_nospace_idx, (last_nospace_idx < equal_idx ? last_nospace_idx : equal_idx) - first_nospace_idx
),
to_enum<T>(StrViewTo64<Signed>(sub.substr(equal_idx + 1)))
);
}
};
size_t first_idx = 0;
size_t second_idx = view.find_first_of(',');
size_t i = 0;
while (second_idx != _string_view::npos) {
enum_map_l[i++] = get_pair(first_idx, second_idx);
first_idx = second_idx + 1;
second_idx = view.find_first_of(',', first_idx);
}
enum_map_l[i] = get_pair(first_idx, view.size());
return enum_map_l;
}
template <EnumT T>
constexpr auto construct_enum_map(
const std::array<std::pair<_string_view, T>, count_enum_values(get_enum_full_string<T>({}))>& enum_array
) {
constexpr size_t sz = count_enum_values(get_enum_full_string<T>({}));
EnumStrHashMap<T, sz> map;
for (const auto& [k, v] : enum_array) { map.insert(v, k); }
return map;
}
template <EnumT T>
requires(requires { construct_enum_array<T>({}); }) struct EnumMapProvider {
constexpr static auto enum_array = construct_enum_array<T>({});
constexpr static auto map = construct_enum_map<T>(enum_array);
};
template <typename T>
concept EnumSupportsMap = EnumT<T> && requires {
{ EnumMapProvider<T>{} };
};
template <EnumSupportsMap T>
class EnumIterator {
size_t index = 0;
constexpr static const auto& s_enum_map = EnumMapProvider<T>::enum_array;
public:
using iterator_category = std::input_iterator_tag;
using value_type = T;
using reference = T&;
constexpr EnumIterator() = default;
constexpr EnumIterator(size_t idx) : index(idx) {}
constexpr const T& operator*() const { return s_enum_map[index].second; }
constexpr const T& operator->() const { return s_enum_map[index].second; }
constexpr bool operator==(const EnumIterator& other) const { return index == other.index; }
constexpr bool operator!=(const EnumIterator& other) const { return index != other.index; }
constexpr EnumIterator& operator++() {
index++;
return *this;
}
constexpr EnumIterator operator++(int) {
EnumIterator iter = *this;
index++;
return iter;
}
constexpr EnumIterator& operator--() {
index--;
return *this;
}
constexpr EnumIterator operator--(int) {
EnumIterator iter = *this;
index--;
return iter;
}
};
} // namespace EnumHelpers
template <EnumT E>
class std::iterator_traits<EnumHelpers::EnumIterator<E>> {
public:
using Iter = EnumHelpers::EnumIterator<E>;
using difference_type = void;
using value_type = E;
using pointer = void;
using reference = E&;
using iterator_category = std::input_iterator_tag;
};
enum class EnumStrSearchType { HashMap, Linear };
template <EnumStrSearchType SearchType = EnumStrSearchType::HashMap, EnumHelpers::EnumSupportsMap T>
constexpr _string_view enum_to_str_view(T e) {
if constexpr (SearchType == EnumStrSearchType::Linear) {
constexpr const auto& enum_map = EnumHelpers::EnumMapProvider<T>::enum_array;
auto it = std::find_if(enum_map.begin(), enum_map.end(), [&e](const auto& p) {
const auto& [name, val] = p;
return val == e;
});
if (it != enum_map.end()) return it->first;
if (std::is_constant_evaluated()) {
// this allocation is here to stop compilation
// if your code reached this allocation it means you passed an invalid value to enum_to_str()
// this *does not* cause leaks because it only gets executed during constexpr evaluation
// and during constexpr evaluation allocations which are not freed in the same scope cause a compilation
// failure because the expression is not constant anymore for example, the error msvc gives you is:
// `error C2131: expression did not evaluate to a constant`
// `Common.h(341): note: failure was caused by allocated storage not being deallocated`
new int[from_enum(e)];
}
return "invalid enum value";
} else {
// I'm not sure if this is better than linear search, it's really just a poor's man linear probing
// hash map with O(1) access time (if no collisions)
// and O(N) max round trip. It will throw if the enum isn't in the map
constexpr const auto& enum_map = EnumHelpers::EnumMapProvider<T>::map;
return enum_map[e];
}
}
template <EnumHelpers::EnumSupportsMap T>
constexpr size_t enum_size() {
constexpr const auto& enum_array = EnumHelpers::EnumMapProvider<T>::enum_array;
return enum_array.size();
}
template <EnumStrSearchType SearchType = EnumStrSearchType::HashMap, EnumHelpers::EnumSupportsMap T>
std::string enum_to_str(T e) {
return std::string{ enum_to_str_view<SearchType, T>(e) };
}
template <EnumHelpers::EnumSupportsMap T>
struct ForEachEnum {
constexpr auto begin() const { return EnumHelpers::EnumIterator<T>{ 0 }; }
constexpr auto end() const {
return EnumHelpers::EnumIterator<T>{ EnumHelpers::EnumMapProvider<T>::enum_array.size() };
}
};
template <EnumHelpers::EnumSupportsMap T>
constexpr auto for_each_enum() {
return ForEachEnum<T>{};
}
template <EnumHelpers::EnumSupportsMap T, typename Functor>
requires(requires { std::declval<Functor>()(std::declval<T>()); }) constexpr auto for_each_enum(Functor func) {
using Res = std::invoke_result_t<Functor, T>;
if constexpr (std::is_void_v<Res>) {
for (auto val : ForEachEnum<T>{}) { func(val); }
} else {
std::array<Res, EnumHelpers::EnumMapProvider<T>::enum_array.size()> result_array;
size_t i = 0;
for (auto val : ForEachEnum<T>{}) { result_array[i++] = func(val); }
return result_array;
}
}
#define ENUM_TO_STR(en, ...) \
namespace EnumHelpers { \
template <> \
constexpr _string_view get_enum_full_string(TagType<en>) { \
return #__VA_ARGS__; \
} \
}
#define MAKE_FANCY_ENUM(en, ...) \
enum class en { __VA_ARGS__ }; \
ENUM_TO_STR(en, __VA_ARGS__)
#define BITMASK_ENUM(en) \
constexpr inline auto operator&(en lhs, en rhs) { \
return to_enum<en>(from_enum(lhs) & from_enum(rhs)); \
} \
constexpr inline auto operator|(en lhs, en rhs) { \
return to_enum<en>(from_enum(lhs) | from_enum(rhs)); \
} \
constexpr inline auto operator^(en lhs, en rhs) { \
return to_enum<en>(from_enum(lhs) ^ from_enum(rhs)); \
} \
constexpr inline auto operator~(en val) { \
return to_enum<en>(~from_enum(val)); \
} \
constexpr inline auto& operator&=(en& lhs, en rhs) { \
lhs = lhs & rhs; \
return lhs; \
} \
constexpr inline auto& operator|=(en& lhs, en rhs) { \
lhs = lhs | rhs; \
return lhs; \
} \
constexpr inline auto& operator^=(en& lhs, en rhs) { \
lhs = lhs ^ rhs; \
return lhs; \
}
[[noreturn]] inline void fatalfail() {
abort();
unreachable;
}
inline void assert_expr(bool expr, const char* msg = nullptr) {
if (!expr) {
if (msg) puts(msg);
#ifdef BACKTRACE_AVAILABLE
BackTrace::print_backtrace();
#endif
fatalfail();
}
}
template <typename T, typename Tp>
concept SameAsConstPtr =
std::same_as<std::add_pointer_t<Tp>, T> || std::same_as<std::add_pointer_t<std::add_const_t<Tp>>, T>;
template <typename T, typename Tp>
concept SameAsConstRef =
std::same_as<std::add_lvalue_reference_t<Tp>, T> || std::same_as<std::add_lvalue_reference_t<std::add_const_t<Tp>>, T>;
template <typename Iter, typename Tp>
concept Iterator = requires(Iter it) {
{ it.operator->() } -> SameAsConstPtr<Tp>;
{ *it } -> SameAsConstRef<Tp>;
{ ++it };
{ it++ };
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment