From d2b9659948bbf234bb86f4c3744f15a5b9bb39ad Mon Sep 17 00:00:00 2001 From: Andrew Gunnerson Date: Tue, 27 Nov 2018 08:06:01 -0500 Subject: [PATCH] libmbcommon: Add support for iterating over Flags objects The iterator is an input iterator (ie. single-pass) that returns the least significant set bit until there are no set bits left. The FlagsIterator class is completely constexpr enabled and assumes two's complement semantics. Signed-off-by: Andrew Gunnerson --- libmbcommon/CMakeLists.txt | 1 + libmbcommon/include/mbcommon/flags.h | 62 ++++++++- libmbcommon/tests/test_flags.cpp | 180 +++++++++++++++++++++++++++ 3 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 libmbcommon/tests/test_flags.cpp diff --git a/libmbcommon/CMakeLists.txt b/libmbcommon/CMakeLists.txt index bed08c46d..faca1db3f 100644 --- a/libmbcommon/CMakeLists.txt +++ b/libmbcommon/CMakeLists.txt @@ -149,6 +149,7 @@ if(variants AND MBP_ENABLE_TESTS) tests/test_file.cpp tests/test_file_error.cpp tests/test_file_util.cpp + tests/test_flags.cpp tests/test_integer.cpp tests/test_locale.cpp tests/test_string.cpp diff --git a/libmbcommon/include/mbcommon/flags.h b/libmbcommon/include/mbcommon/flags.h index 5b4d441a4..78d869402 100644 --- a/libmbcommon/include/mbcommon/flags.h +++ b/libmbcommon/include/mbcommon/flags.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Andrew Gunnerson + * Copyright (C) 2017-2018 Andrew Gunnerson * Copyright (C) 2016 The Qt Company Ltd. * * This file is part of DualBootPatcher @@ -20,6 +20,7 @@ #pragma once +#include #include #include @@ -30,6 +31,55 @@ namespace mb { +template +class FlagsIterator +{ + using Underlying = std::underlying_type_t; + +public: + using difference_type = void; + using value_type = Enum; + using pointer = void; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + + constexpr inline FlagsIterator(Underlying remain) noexcept + : _remain(remain) + { + } + + constexpr inline FlagsIterator & operator++() noexcept + { + _remain &= static_cast(_remain - 1); + return *this; + } + + constexpr inline FlagsIterator operator++(int) noexcept + { + FlagsIterator tmp(*this); + operator++(); + return tmp; + } + + constexpr inline bool operator==(FlagsIterator other) const noexcept + { + return _remain == other._remain; + } + + constexpr inline bool operator!=(FlagsIterator other) const noexcept + { + return !(*this == other); + } + + constexpr inline reference operator*() const noexcept + { + return static_cast(_remain & -_remain); + } + +private: + Underlying _remain; +}; + template class Flags { @@ -147,6 +197,16 @@ class Flags : (*this &= static_cast(~static_cast(f))); } + constexpr inline FlagsIterator begin() const noexcept + { + return FlagsIterator(_value); + } + + constexpr inline FlagsIterator end() const noexcept + { + return FlagsIterator(static_cast(0)); + } + private: Underlying _value; }; diff --git a/libmbcommon/tests/test_flags.cpp b/libmbcommon/tests/test_flags.cpp new file mode 100644 index 000000000..8fc2d7840 --- /dev/null +++ b/libmbcommon/tests/test_flags.cpp @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2018 Andrew Gunnerson + * + * This file is part of DualBootPatcher + * + * DualBootPatcher is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DualBootPatcher is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DualBootPatcher. If not, see . + */ + +#include + +#include "mbcommon/flags.h" + +enum class TestFlag : uint64_t +{ + A = 1ull << 0, + B = 1ull << 1, + C = 1ull << 2, + D = 1ull << 10, + E = 1ull << 20, + F = 1ull << 21, + G = 1ull << 63, +}; +MB_DECLARE_FLAGS(TestFlags, TestFlag) +MB_DECLARE_OPERATORS_FOR_FLAGS(TestFlags) + +using TestFlagType = std::underlying_type_t; + +#define RAW(x) static_cast(x) + +constexpr TestFlags ALL_FLAGS = + TestFlag::A + | TestFlag::B + | TestFlag::C + | TestFlag::D + | TestFlag::E + | TestFlag::F + | TestFlag::G; + +template +bool equals_constexpr(TestFlagType n) +{ + return N == n; +} + +TEST(FlagsTest, CheckZeroFlagConstructor) +{ + TestFlags flags; + ASSERT_EQ(RAW(flags), RAW(0)); +} + +TEST(FlagsTest, CheckSingleFlagConstructor) +{ + TestFlags flags(TestFlag::A); + ASSERT_EQ(RAW(flags), RAW(TestFlag::A)); +} + +TEST(FlagsTest, CheckTestFlag) +{ + auto flags = TestFlag::A | TestFlag::B; + ASSERT_TRUE(flags.test_flag(TestFlag::A)); + ASSERT_TRUE(flags.test_flag(TestFlag::B)); + ASSERT_FALSE(flags.test_flag(TestFlag::C)); +} + +TEST(FlagsTest, CheckSetFlag) +{ + TestFlags flags(TestFlag::A); + flags.set_flag(TestFlag::B, true); + ASSERT_EQ(RAW(flags), RAW(TestFlag::A) | RAW(TestFlag::B)); + flags.set_flag(TestFlag::A, false); + ASSERT_EQ(RAW(flags), RAW(TestFlag::B)); + flags.set_flag(TestFlag::B, false); + ASSERT_EQ(RAW(flags), RAW(0)); + flags.set_flag(TestFlag::C, true); + ASSERT_EQ(RAW(flags), RAW(TestFlag::C)); +} + +TEST(FlagsTest, CheckNotOperator) +{ + TestFlags flags; + ASSERT_FALSE(flags); + ASSERT_TRUE(!flags); + flags |= TestFlag::A; + ASSERT_TRUE(flags); + ASSERT_FALSE(!flags); +} + +TEST(FlagsTest, CheckArithmeticAssignmentOperators) +{ + TestFlags flags; + flags |= TestFlag::A; + ASSERT_EQ(RAW(flags), RAW(TestFlag::A)); + flags |= TestFlags(TestFlag::B); + ASSERT_EQ(RAW(flags), RAW(TestFlag::A) | RAW(TestFlag::B)); + flags ^= TestFlag::B; + ASSERT_EQ(RAW(flags), RAW(TestFlag::A)); + flags ^= TestFlags(TestFlag::C); + ASSERT_EQ(RAW(flags), RAW(TestFlag::A) | RAW(TestFlag::C)); + flags &= TestFlag::A; + ASSERT_EQ(RAW(flags), RAW(TestFlag::A)); + flags &= TestFlags(TestFlag::A); + ASSERT_EQ(RAW(flags), RAW(TestFlag::A)); +} + +TEST(FlagsTest, CheckArithmeticNonAssignmentOperators) +{ + ASSERT_EQ(RAW(TestFlags(TestFlag::A) | TestFlag::B), + RAW(TestFlag::A) | RAW(TestFlag::B)); + ASSERT_EQ(RAW(TestFlags(TestFlag::A) | TestFlags(TestFlag::B)), + RAW(TestFlag::A) | RAW(TestFlag::B)); + ASSERT_EQ(RAW(TestFlags(TestFlag::A) ^ TestFlag::A), + RAW(0)); + ASSERT_EQ(RAW(TestFlags(TestFlag::A) ^ TestFlags(TestFlag::B)), + RAW(TestFlag::A) | RAW(TestFlag::B)); + ASSERT_EQ(RAW(TestFlags(TestFlag::A) & TestFlag::A), + RAW(TestFlag::A)); + ASSERT_EQ(RAW(TestFlags(TestFlag::A) & TestFlags(TestFlag::B)), + RAW(0)); + ASSERT_EQ(RAW(~TestFlags() ^ TestFlags()), + ~RAW(0)); +} + +TEST(FlagsTest, CheckConstExprOperations) +{ + ASSERT_TRUE(equals_constexpr( + RAW(TestFlag::A | TestFlag::B))); + ASSERT_TRUE(equals_constexpr( + RAW(0))); + ASSERT_TRUE(equals_constexpr( + RAW(TestFlag::A))); + ASSERT_TRUE(equals_constexpr<(TestFlag::A | TestFlag::B) ^ TestFlag::B>( + RAW(TestFlag::A))); +} + +TEST(FlagsTest, IterateNormalFlags) +{ + auto it = ALL_FLAGS.begin(); + ASSERT_EQ(*it++, TestFlag::A); + ASSERT_EQ(*it++, TestFlag::B); + ASSERT_EQ(*it++, TestFlag::C); + ASSERT_EQ(*it++, TestFlag::D); + ASSERT_EQ(*it++, TestFlag::E); + ASSERT_EQ(*it++, TestFlag::F); + ASSERT_EQ(*it++, TestFlag::G); + ASSERT_EQ(it, ALL_FLAGS.end()); +} + +TEST(FlagsTest, IterateEmptyFlags) +{ + TestFlags flags; + ASSERT_EQ(flags.begin(), flags.end()); +} + +TEST(FlagsTest, CheckIteratorEquality) +{ + auto it1 = ALL_FLAGS.begin(); + auto it2 = ALL_FLAGS.begin(); + + ++it1; + ASSERT_NE(it1, it2); + ++it2; + ASSERT_EQ(it1, it2); +} + +TEST(FlagsTest, CheckConstExprIterator) +{ + ASSERT_TRUE(equals_constexpr( + RAW(TestFlag::A))); +}