Skip to content
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
113 changes: 113 additions & 0 deletions include/iris/enum_bitops.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#ifndef IRIS_ENUM_BITOPS_HPP
#define IRIS_ENUM_BITOPS_HPP

// SPDX-License-Identifier: MIT

#include <concepts>
#include <type_traits>
#include <utility>

#ifndef NDEBUG
#include <bit>
#endif

#include <cassert>

namespace iris {

template<class T>
struct bitops_enabled : std::false_type {};

template<class T>
constexpr bool bitops_enabled_v = bitops_enabled<T>::value;

namespace detail {

template<class T>
concept bitops_enum_has_min_bit = std::same_as<std::remove_const_t<decltype(bitops_enabled<T>::min_bit)>, int>;

template<class T>
concept bitops_enum_has_max_bit = std::same_as<std::remove_const_t<decltype(bitops_enabled<T>::max_bit)>, int>;

} // detail

template<class T>
concept BitopsEnabledEnum = std::is_enum_v<T> && bitops_enabled_v<T>;

inline namespace bitops_operators {

template<BitopsEnabledEnum T>
[[nodiscard]] constexpr T operator~(T a) noexcept
{
return static_cast<T>(~std::to_underlying(a));
}

template<BitopsEnabledEnum T>
[[nodiscard]] constexpr T operator|(T a, T b) noexcept
{
static_assert(std::is_unsigned_v<std::underlying_type_t<T>>);
return static_cast<T>(std::to_underlying(a) | std::to_underlying(b));
}

template<BitopsEnabledEnum T>
constexpr T& operator|=(T& a, T b) noexcept
{
static_assert(std::is_unsigned_v<std::underlying_type_t<T>>);
return a = a | b;
}

template<BitopsEnabledEnum T>
[[nodiscard]] constexpr T operator&(T a, T b) noexcept
{
static_assert(std::is_unsigned_v<std::underlying_type_t<T>>);
return static_cast<T>(std::to_underlying(a) & std::to_underlying(b));
}

template<BitopsEnabledEnum T>
constexpr T& operator&=(T& a, T b) noexcept
{
static_assert(std::is_unsigned_v<std::underlying_type_t<T>>);
return a = a & b;
}

template<BitopsEnabledEnum T>
[[nodiscard]] constexpr T operator^(T a, T b) noexcept
{
static_assert(std::is_unsigned_v<std::underlying_type_t<T>>);
return static_cast<T>(std::to_underlying(a) ^ std::to_underlying(b));
}

template<BitopsEnabledEnum T>
constexpr T& operator^=(T& a, T b) noexcept
{
static_assert(std::is_unsigned_v<std::underlying_type_t<T>>);
return a = a ^ b;
}

} // bitops_operators

template<BitopsEnabledEnum T>
[[nodiscard]] constexpr bool contains(T a, T b) noexcept
{
static_assert(std::is_unsigned_v<std::underlying_type_t<T>>);
return (std::to_underlying(a) & std::to_underlying(b)) == std::to_underlying(b);
}

template<BitopsEnabledEnum T>
[[nodiscard]] constexpr bool contains_single_bit(T a, T b) noexcept
{
static_assert(std::is_unsigned_v<std::underlying_type_t<T>>);
assert(std::has_single_bit(std::to_underlying(b)));
return std::to_underlying(a) & std::to_underlying(b);
}

template<BitopsEnabledEnum T>
[[nodiscard]] constexpr bool contains_any_bit(T a, T b) noexcept
{
static_assert(std::is_unsigned_v<std::underlying_type_t<T>>);
return std::to_underlying(a) & std::to_underlying(b);
}

} // iris

#endif
35 changes: 35 additions & 0 deletions include/iris/enum_bitops_algorithm.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#ifndef IRIS_ENUM_BITOPS_ALGORITHM_HPP
#define IRIS_ENUM_BITOPS_ALGORITHM_HPP

// SPDX-License-Identifier: MIT

#include <iris/enum_bitops.hpp>

#include <limits>
#include <ranges>
#include <utility>

namespace iris {

template <BitopsEnabledEnum T>
[[nodiscard]] constexpr auto each_bit(T flags) noexcept
{
static_assert(std::is_unsigned_v<std::underlying_type_t<T>>);
static_assert(detail::bitops_enum_has_max_bit<T>);
static_assert(bitops_enabled<T>::max_bit < std::numeric_limits<std::underlying_type_t<T>>::digits);

if constexpr (detail::bitops_enum_has_min_bit<T>) {
static_assert(bitops_enabled<T>::min_bit <= bitops_enabled<T>::max_bit);
return std::views::iota(bitops_enabled<T>::min_bit, bitops_enabled<T>::max_bit + 1)
| std::views::filter([cat = std::to_underlying(flags)](int i) constexpr noexcept -> bool { return (cat >> i) & 1; })
| std::views::transform([](int i) constexpr noexcept { return static_cast<T>(static_cast<std::underlying_type_t<T>>(1) << i); });
} else {
return std::views::iota(0, bitops_enabled<T>::max_bit + 1)
| std::views::filter([cat = std::to_underlying(flags)](int i) constexpr noexcept -> bool { return (cat >> i) & 1; })
| std::views::transform([](int i) constexpr noexcept { return static_cast<T>(static_cast<std::underlying_type_t<T>>(1) << i); });
}
}

} // iris

#endif
97 changes: 97 additions & 0 deletions include/iris/enum_bitops_io.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#ifndef IRIS_ENUM_BITOPS_IO_HPP
#define IRIS_ENUM_BITOPS_IO_HPP

// SPDX-License-Identifier: MIT

#include <iris/string.hpp>
#include <iris/enum_bitops.hpp>

#include <ostream>
#include <format>
#include <ranges>
#include <string>
#include <string_view>
#include <utility>

namespace iris {

inline namespace bitops_operators {

template<BitopsEnabledEnum T>
std::ostream& operator<<(std::ostream& os, T const& val)
{
using std::to_string;

if constexpr (requires {
{ to_string(val) } -> std::convertible_to<std::string const&>;
}) {
return os << to_string(val);
} else {
return os << std::to_underlying(val);
}
}

} // bitops_operators

template<BitopsEnabledEnum T>
[[nodiscard]] constexpr T parse_flag(StringLike auto const& str) noexcept
{
return bitops_enabled<T>::parse(std::basic_string_view{str});
}

template<BitopsEnabledEnum T>
[[nodiscard]] constexpr T parse_flags(StringLike auto const& str, StringLike auto const& delim) noexcept
{
using namespace iris::bitops_operators;

std::basic_string_view str_sv{str};
std::basic_string_view delim_sv{delim};

T res{};

for (auto const& r : str_sv | std::views::split(delim_sv)) {
std::basic_string_view const part_str{r.begin(), r.end()};

auto const part = iris::parse_flag<T>(part_str);
if (part == T{}) {
return T{};
}
res |= part;
}

return res;
}

} // iris

namespace std {

template<::iris::BitopsEnabledEnum T, class CharT>
struct formatter<T, CharT> : formatter<std::underlying_type_t<T>, CharT>
{
using base_formatter = formatter<std::underlying_type_t<T>, CharT>;

template<class Context>
constexpr auto parse(Context& ctx)
{
if (ctx.begin() == ctx.end()) return ctx.end();
has_format_spec_ = true;
return base_formatter::parse(ctx);
}

template<class Context>
auto format(T const& val, Context& ctx) const
{
if (has_format_spec_) {
return base_formatter::format(std::to_underlying(val), ctx);
}
return format_to(ctx.out(), "{}", to_string(val));
}

private:
bool has_format_spec_ = false;
};

} // std

#endif
18 changes: 18 additions & 0 deletions include/iris/string.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#ifndef IRIS_STRING_HPP
#define IRIS_STRING_HPP

// SPDX-License-Identifier: MIT

#include <string>
#include <string_view>

namespace iris {

template<class T>
concept StringLike = requires(T t) {
std::basic_string_view{t};
};

} // iris

#endif
3 changes: 2 additions & 1 deletion test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,9 @@ if(PROJECT_IS_TOP_LEVEL)

set(
IRIS_TEST_IRIS_TESTS
type_traits
core
type_traits
enum
indirect
)

Expand Down
108 changes: 108 additions & 0 deletions test/enum.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// SPDX-License-Identifier: MIT

#include "iris_test.hpp"

#include <iris/enum_bitops.hpp>
#include <iris/enum_bitops_algorithm.hpp>
#include <iris/enum_bitops_io.hpp>

#include <algorithm>
#include <ranges>
#include <string_view>
#include <utility>
#include <vector>

#include <cstdint>

enum class MyFlags : std::uint8_t
{
FOO = 1 << 0,
BAR = 1 << 1,
BAZ = 1 << 2,
};

enum class SpellType : std::uint8_t
{
TYPE_ATTACK = 1 << 0,
TYPE_DEFENSE = 1 << 1,

ATTR_FIRE = 1 << 2,
ATTR_WATER = 1 << 3,
ATTR_THUNDER = 1 << 4,
};

template <>
struct iris::bitops_enabled<MyFlags> : std::true_type
{
static MyFlags parse(std::string_view sv) noexcept
{
using enum MyFlags;
if (sv == "foo") return FOO;
if (sv == "bar") return BAR;
if (sv == "baz") return BAZ;
return {};
}
};

template <>
struct iris::bitops_enabled<SpellType> : std::true_type
{
static constexpr int min_bit = 2;
static constexpr int max_bit = 4;
};

TEST_CASE("enum")
{
using namespace iris::bitops_operators;

using enum MyFlags;

CHECK((~FOO == static_cast<MyFlags>(~std::to_underlying(FOO))));

CHECK(((FOO & BAR) == static_cast<MyFlags>(std::to_underlying(FOO) & std::to_underlying(BAR))));
CHECK(((FOO ^ BAR) == static_cast<MyFlags>(std::to_underlying(FOO) ^ std::to_underlying(BAR))));
CHECK(((FOO | BAR) == static_cast<MyFlags>(std::to_underlying(FOO) | std::to_underlying(BAR))));

CHECK( iris::contains(FOO, FOO));
CHECK(!iris::contains(FOO, BAR));
CHECK( iris::contains(FOO | BAR, FOO));
CHECK(!iris::contains(FOO | BAR, BAZ));
CHECK(!iris::contains(FOO | BAZ, FOO | BAR));
CHECK(!iris::contains( BAR | BAZ, FOO | BAR));
CHECK( iris::contains(FOO | BAR | BAZ, FOO | BAR));

CHECK( iris::contains_any_bit(FOO, FOO));
CHECK(!iris::contains_any_bit(FOO, BAR));
CHECK( iris::contains_any_bit(FOO | BAR, FOO));
CHECK(!iris::contains_any_bit(FOO | BAR, BAZ));
CHECK( iris::contains_any_bit(FOO | BAZ, FOO | BAR));
CHECK( iris::contains_any_bit( BAR | BAZ, FOO | BAR));
CHECK( iris::contains_any_bit(FOO | BAR | BAZ, FOO | BAR));

CHECK( iris::contains_single_bit(FOO, FOO));
CHECK(!iris::contains_single_bit(FOO, BAR));
CHECK( iris::contains_single_bit(FOO | BAR, FOO));
CHECK(!iris::contains_single_bit(FOO | BAR, BAZ));

CHECK((iris::parse_flag<MyFlags>("foo") == FOO));
CHECK((iris::parse_flag<MyFlags>("bar") == BAR));
CHECK((iris::parse_flag<MyFlags>("baz") == BAZ));
CHECK((iris::parse_flag<MyFlags>("yay") == MyFlags{}));

CHECK((iris::parse_flags<MyFlags>("foo", "|") == FOO));
CHECK((iris::parse_flags<MyFlags>("yay", "|") == MyFlags{}));

CHECK((iris::parse_flags<MyFlags>("foo|bar", "|") == (FOO | BAR)));
CHECK((iris::parse_flags<MyFlags>("foo|yay", "|") == MyFlags{}));
CHECK((iris::parse_flags<MyFlags>("foo,bar", "|") == MyFlags{}));

CHECK(std::ranges::equal(
iris::each_bit(SpellType::TYPE_ATTACK | SpellType::ATTR_FIRE | SpellType::ATTR_THUNDER),
std::vector{SpellType::ATTR_FIRE, SpellType::ATTR_THUNDER}
));

CHECK(std::ranges::equal(
iris::each_bit(static_cast<SpellType>(static_cast<std::underlying_type_t<SpellType>>(-1))),
std::vector{SpellType::ATTR_FIRE, SpellType::ATTR_WATER, SpellType::ATTR_THUNDER}
));
}