Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: cleanup some parts of qmagicenum #5587

Merged
merged 2 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
- Dev: Refactored `MessageBuilder` to be a single class. (#5548)
- Dev: Recent changes are now shown in the nightly release description. (#5553, #5554)
- Dev: The timer for `StreamerMode` is now destroyed on the correct thread. (#5571)
- Dev: Cleanup some parts of the `magic_enum` adaptation for Qt. (#5587)

## 2.5.1

Expand Down
177 changes: 84 additions & 93 deletions src/util/QMagicEnum.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,30 @@
#include <QString>
#include <QStringView>

#include <concepts>

namespace chatterino::qmagicenum::detail {

template <bool, typename R>
struct EnableIfEnum {
};
template <typename T, typename U>
concept DecaysTo = std::same_as<std::decay_t<T>, U>;

template <typename R>
struct EnableIfEnum<true, R> {
using type = R;
};
template <typename T>
concept IsEnum = std::is_enum_v<T>;

template <typename BinaryPredicate>
consteval bool isDefaultPredicate() noexcept
{
return std::is_same_v<std::decay_t<BinaryPredicate>,
std::equal_to<QChar>> ||
std::is_same_v<std::decay_t<BinaryPredicate>, std::equal_to<>>;
}

template <typename T, typename R, typename BinaryPredicate = std::equal_to<>,
typename D = std::decay_t<T>>
using enable_if_t = typename EnableIfEnum<
std::is_enum_v<D> &&
std::is_invocable_r_v<bool, BinaryPredicate, QChar, QChar>,
R>::type;
template <typename BinaryPredicate>
consteval bool isNothrowInvocable()
{
return isDefaultPredicate<BinaryPredicate>() ||
std::is_nothrow_invocable_r_v<bool, BinaryPredicate, QChar, QChar>;
}

template <std::size_t N>
consteval QStringView fromArray(const std::array<char16_t, N> &arr)
Expand All @@ -43,12 +50,12 @@ consteval bool isLatin1(std::string_view maybe)
}

template <typename BinaryPredicate>
inline constexpr bool eq(
constexpr bool eq(
QStringView a, QStringView b,
[[maybe_unused]] BinaryPredicate &&
p) noexcept(magic_enum::detail::is_nothrow_invocable<BinaryPredicate>())
{
// Note: operator== isn't constexpr
// Note: QStringView::operator== isn't constexpr
if (a.size() != b.size())
{
return false;
Expand Down Expand Up @@ -82,24 +89,22 @@ consteval auto enumNameStorage()
return storage;
}

/// This contains a std::array<char16_t> for each enum value (V) with the
/// corresponding name.
template <typename E, E V>
inline constexpr auto ENUM_NAME_STORAGE = enumNameStorage<char16_t, E, V>();

template <typename E, magic_enum::detail::enum_subtype S, std::size_t... I>
template <typename E, std::size_t... I>
consteval auto namesStorage(std::index_sequence<I...> /*unused*/)
{
return std::array<QStringView, sizeof...(I)>{{detail::fromArray(
ENUM_NAME_STORAGE<E, magic_enum::enum_values<E, S>()[I]>)...}};
ENUM_NAME_STORAGE<E, magic_enum::enum_values<E>()[I]>)...}};
}

template <typename E,
magic_enum::detail::enum_subtype S = magic_enum::detail::subtype_v<E>>
inline constexpr auto NAMES_STORAGE = namesStorage<E, S>(
std::make_index_sequence<magic_enum::enum_count<E, S>()>{});

template <typename E, magic_enum::detail::enum_subtype S,
typename D = std::decay_t<E>>
using NamesStorage = decltype((NAMES_STORAGE<D, S>));
/// This contains a std::array<QStringView> for each enum (E).
template <typename E>
inline constexpr auto NAMES_STORAGE =
namesStorage<E>(std::make_index_sequence<magic_enum::enum_count<E>()>{});

template <typename Op = std::equal_to<>>
class CaseInsensitive
Expand All @@ -112,16 +117,34 @@ class CaseInsensitive
}

public:
template <typename L, typename R>
constexpr std::enable_if_t<std::is_same_v<std::decay_t<L>, QChar> &&
std::is_same_v<std::decay_t<R>, QChar>,
bool>
operator()(L lhs, R rhs) const noexcept
template <DecaysTo<QChar> L, DecaysTo<QChar> R>
constexpr bool operator()(L lhs, R rhs) const noexcept
{
return Op{}(toLower(lhs), toLower(rhs));
}
};

/// @brief Gets a static QString from @a view.
///
/// @pre @a view must be a static string view (i.e. it must be valid throughout
/// the entire duration of the program).
///
/// @param view The view to turn into a static string
/// @returns Qt6: A static string (never gets freed), Qt5: regular string
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
[[nodiscard]] inline QString staticString(QStringView view) noexcept
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
return QString(QStringPrivate(nullptr, const_cast<char16_t *>(view.utf16()),
view.size()));
}
#else
[[nodiscard]] inline QString staticString(QStringView view)
{
return view.toString();
}
#endif

} // namespace chatterino::qmagicenum::detail

namespace chatterino::qmagicenum {
Expand All @@ -132,9 +155,8 @@ namespace chatterino::qmagicenum {
///
/// @tparam V The enum value
/// @returns The name as a string view
template <auto V>
[[nodiscard]] consteval detail::enable_if_t<decltype(V), QStringView>
enumName() noexcept
template <detail::IsEnum auto V>
[[nodiscard]] consteval QStringView enumName() noexcept
{
return QStringView{
detail::fromArray(detail::ENUM_NAME_STORAGE<decltype(V), V>)};
Expand All @@ -145,53 +167,29 @@ template <auto V>
/// @param value The enum value
/// @returns The name as a string view. If @a value does not have name or the
/// value is out of range an empty string is returned.
template <typename E,
magic_enum::detail::enum_subtype S = magic_enum::detail::subtype_v<E>>
[[nodiscard]] constexpr detail::enable_if_t<E, QStringView> enumName(
E value) noexcept
template <detail::IsEnum E>
[[nodiscard]] constexpr QStringView enumName(E value) noexcept
{
using D = std::decay_t<E>;

if (const auto i = magic_enum::enum_index<D, S>(value))
if (const auto i = magic_enum::enum_index<D>(value))
{
return detail::NAMES_STORAGE<D, S>[*i];
return detail::NAMES_STORAGE<D>[*i];
}
return {};
}

/// @brief Gets a static QString from @a view.
///
/// @pre @a view must be a static string view (i.e. it must be valid throughout
/// the entire duration of the program).
///
/// @param view The view to turn into a static string
/// @returns Qt6: A static string (never gets freed), Qt5: regular string
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
[[nodiscard]] inline QString staticString(QStringView view) noexcept
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
return QString(QStringPrivate(nullptr, const_cast<char16_t *>(view.utf16()),
view.size()));
}
#else
[[nodiscard]] inline QString staticString(QStringView view)
{
return view.toString();
}
#endif

/// @brief Get the name of an enum value
///
/// This version is much lighter on the compile times and is not restricted to
/// the enum_range limitation.
///
/// @tparam V The enum value
/// @returns The name as a string. The returned string is static.
template <auto V>
[[nodiscard]] inline detail::enable_if_t<decltype(V), QString>
enumNameString() noexcept
template <detail::IsEnum auto V>
[[nodiscard]] inline QString enumNameString() noexcept
{
return staticString(enumName<V>());
return detail::staticString(enumName<V>());
}

/// @brief Get the name of an enum value
Expand All @@ -203,14 +201,12 @@ template <auto V>
/// @returns The name as a string. If @a value does not have name or the
/// value is out of range an empty string is returned.
/// The returned string is static.
template <typename E,
magic_enum::detail::enum_subtype S = magic_enum::detail::subtype_v<E>>
[[nodiscard]] inline detail::enable_if_t<E, QString> enumNameString(
E value) noexcept
template <detail::IsEnum E>
[[nodiscard]] inline QString enumNameString(E value) noexcept
{
using D = std::decay_t<E>;

return staticString(enumName<D, S>(value));
return detail::staticString(enumName<D>(value));
}

/// @brief Gets the enum value from a name
Expand All @@ -221,29 +217,25 @@ template <typename E,
/// (defaults to std::equal_to)
/// @returns A `std::optional` of the parsed value. If no value was parsed,
/// `std::nullopt` is returned.
template <typename E,
magic_enum::detail::enum_subtype S = magic_enum::detail::subtype_v<E>,
typename BinaryPredicate = std::equal_to<>>
[[nodiscard]] constexpr detail::enable_if_t<E, std::optional<std::decay_t<E>>,
BinaryPredicate>
enumCast(QStringView name,
[[maybe_unused]] BinaryPredicate p =
{}) noexcept(magic_enum::detail::
is_nothrow_invocable<BinaryPredicate>())
template <detail::IsEnum E, typename BinaryPredicate = std::equal_to<>>
[[nodiscard]] constexpr std::optional<std::decay_t<E>>
enumCast(QStringView name, BinaryPredicate p = {}) noexcept(
detail::isNothrowInvocable<BinaryPredicate>())
requires std::is_invocable_r_v<bool, BinaryPredicate, QChar, QChar>
{
using D = std::decay_t<E>;

if constexpr (magic_enum::enum_count<D, S>() == 0)
if constexpr (magic_enum::enum_count<D>() == 0)
{
static_cast<void>(name);
return std::nullopt; // Empty enum.
}

for (std::size_t i = 0; i < magic_enum::enum_count<D, S>(); i++)
for (std::size_t i = 0; i < magic_enum::enum_count<D>(); i++)
{
if (detail::eq(name, detail::NAMES_STORAGE<D, S>[i], p))
if (detail::eq(name, detail::NAMES_STORAGE<D>[i], p))
{
return magic_enum::enum_value<D, S>(i);
return magic_enum::enum_value<D>(i);
}
}
return std::nullopt; // Invalid value or out of range.
Expand All @@ -256,22 +248,23 @@ template <typename E,
/// @returns A string containing all names separated by @a sep. If any flag in
/// @a flags is out of rage or does not have a name, an empty string
/// is returned.
template <typename E>
[[nodiscard]] inline detail::enable_if_t<E, QString> enumFlagsName(
E flags, char16_t sep = u'|')
template <detail::IsEnum E>
[[nodiscard]] inline QString enumFlagsName(E flags, char16_t sep = u'|')
{
using D = std::decay_t<E>;
using U = std::underlying_type_t<D>;
constexpr auto S = magic_enum::detail::enum_subtype::flags; // NOLINT
static_assert(magic_enum::detail::subtype_v<E> ==
magic_enum::detail::enum_subtype::flags,
"enumFlagsName used for non-flags enum");

QString name;
auto checkValue = U{0};
for (std::size_t i = 0; i < magic_enum::enum_count<D, S>(); ++i)
for (std::size_t i = 0; i < magic_enum::enum_count<D>(); ++i)
{
const auto v = static_cast<U>(magic_enum::enum_value<D, S>(i));
const auto v = static_cast<U>(magic_enum::enum_value<D>(i));
if ((static_cast<U>(flags) & v) != 0)
{
const auto n = detail::NAMES_STORAGE<D, S>[i];
const auto n = detail::NAMES_STORAGE<D>[i];
if (!n.empty())
{
checkValue |= v;
Expand Down Expand Up @@ -299,12 +292,10 @@ template <typename E>
///
/// @tparam E The enum type
/// @returns A `std::array` of all names (`QStringView`s)
template <typename E,
magic_enum::detail::enum_subtype S = magic_enum::detail::subtype_v<E>>
template <detail::IsEnum E>
[[nodiscard]] constexpr auto enumNames() noexcept
-> detail::enable_if_t<E, detail::NamesStorage<E, S>>
{
return detail::NAMES_STORAGE<std::decay_t<E>, S>;
return detail::NAMES_STORAGE<std::decay_t<E>>;
}

/// Allows you to write qmagicenum::enumCast<foo>("bar", qmagicenum::CASE_INSENSITIVE)
Expand Down
5 changes: 4 additions & 1 deletion tests/src/QMagicEnum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ enum class MyFlag {
Two = 2,
Four = 4,
Eight = 8,
TwoPow9 = 512,
TwoPow10 = 1024,
};
using MyFlags = chatterino::FlagsEnum<MyFlag>;

Expand Down Expand Up @@ -130,7 +132,8 @@ TEST(QMagicEnum, flags)
static_assert(checkConst(MyFlag::Eight, u"Eight"));
static_assert(checkConst(MyFlag::Eight, u"Eight"));
static_assert(eq(enumName(static_cast<MyFlag>(16)), u""));
static_assert(checkValues<MyFlag>({u"One", u"Two", u"Four", u"Eight"}));
static_assert(checkValues<MyFlag>(
{u"One", u"Two", u"Four", u"Eight", u"TwoPow9", u"TwoPow10"}));
}

TEST(QMagicEnum, enumNameString)
Expand Down
Loading