Last active
November 7, 2022 13:44
-
-
Save Trass3r/6fae8f543863a8853226d8c086efe7b1 to your computer and use it in GitHub Desktop.
proper enum flags based on conventional "Flags" suffix (try with -Wall -W -Og -std=c++17 / -std:c++17 -O1)
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
#pragma once | |
/*! | |
* Copyright (C) 2022 Andreas Hollandt | |
* | |
* Distributed under the Boost Software License, Version 1.0. | |
* See copy at http://boost.org/LICENSE_1_0.txt. | |
*/ | |
// requires clang 3.6, msvc 2017 or gcc 9 (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66639) | |
#include <type_traits> | |
#include <stddef.h> | |
namespace detail | |
{ | |
// using own type to avoid the include and various constexpr limitations or missing methods like ends_with | |
struct StringView | |
{ | |
const char* data = ""; | |
size_t size = 0; | |
constexpr StringView() = default; | |
template <size_t N> | |
constexpr StringView(const char (&str)[N]) { data = str; size = N-1; } | |
template <size_t N> | |
constexpr void operator=(const char (&str)[N]) { data = str; size = N-1; } | |
constexpr size_t find(char c) | |
{ | |
for (size_t i = 0; i < size; ++i) | |
if (data[i] == c) | |
return i; | |
return ~0u; | |
} | |
constexpr void remove_prefix(size_t n) | |
{ | |
if (n > size) | |
n = size; | |
data += n; | |
size -= n; | |
} | |
constexpr void remove_suffix(size_t n) | |
{ | |
if (n > size) | |
n = size; | |
size -= n; | |
} | |
constexpr int compare(size_t pos, StringView str) const | |
{ | |
for (size_t i = 0; i < str.size; ++i) | |
if (data[pos + i] != str.data[i]) | |
return 1; | |
return 0; | |
} | |
constexpr bool operator==(StringView s) const | |
{ return size == s.size && compare(0, s) == 0; } | |
constexpr bool ends_with(StringView suffix) const | |
{ | |
return size >= suffix.size && compare(size - suffix.size, suffix) == 0; | |
} | |
}; | |
template <typename T> | |
constexpr auto typeName_() | |
{ | |
StringView name; | |
#if defined(__clang__) || defined(__GNUC__) | |
name = __PRETTY_FUNCTION__; // "constexpr auto typeName() [with T = ...]" | |
name.remove_prefix(name.find('=') + 2); | |
name.remove_suffix(sizeof(']')); | |
#elif defined(_MSC_VER) | |
name = __FUNCSIG__; // "auto __cdecl typeName<...>(void)" | |
name.remove_prefix(name.find('<') + 1); | |
name.remove_suffix(sizeof(">(void)")-1); | |
#endif | |
return name; | |
} | |
template <typename T> | |
constexpr StringView typeName = typeName_<T>(); | |
static_assert(typeName<StringView>.ends_with("StringView"), "fail"); | |
template <auto E> | |
constexpr auto enumName_() | |
{ | |
StringView name; | |
#if defined(__clang__) || defined(__GNUC__) | |
name = __PRETTY_FUNCTION__; // "constexpr auto enumName() [with auto E = ...]" | |
// utest::TestFlags::C vs (utest::TestFlags)3 | |
name.remove_prefix(name.find('=') + 2); | |
name.remove_suffix(sizeof(']')); | |
#elif defined(_MSC_VER) | |
name = __FUNCSIG__; // "auto __cdecl enumName<...>(void)" | |
// utest::TestFlags::C vs. (enum utest::TestFlags)0x3 | |
name.remove_prefix(name.find('<') + 1); | |
name.remove_suffix(sizeof(">(void)")-1); | |
#endif | |
return name; | |
} | |
template <auto T> | |
constexpr StringView enumName = enumName_<T>(); | |
} | |
template <typename T> | |
using is_scoped_enum = std::integral_constant<bool, std::is_enum<std::decay_t<T>>::value && !std::is_convertible<T, int>::value>; | |
template <typename T> | |
constexpr bool is_scoped_enum_v = is_scoped_enum<T>::value; | |
template <typename T, typename = | |
std::enable_if_t<is_scoped_enum_v<T>>> | |
constexpr auto castToBaseType(T t) | |
{ | |
return std::underlying_type_t<T>(t); | |
} | |
template <typename T> | |
constexpr bool is_flags_enum = is_scoped_enum_v<T> && detail::typeName<std::decay_t<T>>.ends_with("Flags"); | |
template <typename T, typename = | |
std::enable_if_t<is_flags_enum<T>>> | |
constexpr T operator|(T lhs, T rhs) | |
{ | |
return (T)(castToBaseType(lhs) | castToBaseType(rhs)); | |
} | |
template <typename T, typename = | |
std::enable_if_t<is_flags_enum<T>>> | |
constexpr T operator&(T lhs, T rhs) | |
{ | |
return (T)(castToBaseType(lhs) & castToBaseType(rhs)); | |
} | |
template <typename T, typename = | |
std::enable_if_t<is_flags_enum<T>>> | |
constexpr bool operator!(T lhs) | |
{ | |
return !castToBaseType(lhs); | |
} | |
namespace utest | |
{ | |
enum class TestScoped : unsigned long long {}; | |
enum TestUnScoped {}; | |
enum class TestFlags { A, B, C }; | |
static_assert(is_scoped_enum_v<TestScoped>, "fail"); | |
static_assert(is_scoped_enum_v<const TestScoped&>, "fail"); | |
static_assert(!is_scoped_enum_v<TestUnScoped>, "fail"); | |
static_assert(!is_scoped_enum_v<TestUnScoped&&>, "fail"); | |
static_assert(!is_flags_enum<TestScoped>, "fail"); | |
static_assert(!is_flags_enum<const TestScoped>, "fail"); | |
static_assert(is_flags_enum<TestFlags>, "fail"); | |
static_assert(is_flags_enum<const TestFlags&>, "fail"); | |
static_assert(castToBaseType(TestFlags::A & TestFlags::B) == 0, "fail"); | |
static_assert(!(TestFlags::A & TestFlags::B), "fail"); | |
static_assert(!!(TestFlags::A | TestFlags::B), "fail"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment