Skip to content

Commit

Permalink
8257466: Improve enum iteration
Browse files Browse the repository at this point in the history
Improve support for iteration on enums that are just range of values, without named enumerators.

Reviewed-by: iklam, lfoltan
  • Loading branch information
Kim Barrett committed Dec 3, 2020
1 parent 02a0a02 commit 3932527
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 22 deletions.
89 changes: 67 additions & 22 deletions src/hotspot/share/utilities/enumIterator.hpp
Expand Up @@ -28,6 +28,7 @@
#include <type_traits>
#include <limits>
#include "memory/allStatic.hpp"
#include "metaprogramming/enableIf.hpp"
#include "utilities/debug.hpp"

// Iteration support for enums.
Expand Down Expand Up @@ -73,21 +74,52 @@
// }

// EnumeratorRange is a traits type supporting iteration over the enumerators of T.
// Specializations must provide static const data members named
// "_first" and "_last", whose values are the smallest / largest
// (resp.) enumerator values for T. For iteration, the enumerators of
// T must have sequential values in that range.
// Specializations must provide static const data members named "_start" and "_end".
// The type of _start and _end must be the underlying type of T.
// _start is the inclusive lower bound of values in the range.
// _end is the exclusive upper bound of values in the range.
// The enumerators of T must have sequential values in that range.
template<typename T> struct EnumeratorRange;

// Specialize EnumeratorRange<T>.
#define ENUMERATOR_RANGE(T, First, Last) \
template<> struct EnumeratorRange<T> { \
static constexpr T _first = First; \
static constexpr T _last = Last; \
// Helper class for ENUMERATOR_RANGE and ENUMERATOR_VALUE_RANGE.
struct EnumeratorRangeImpl : AllStatic {
template<typename T> using Underlying = std::underlying_type_t<T>;

// T not deduced to verify argument is of expected type.
template<typename T, typename U, ENABLE_IF(std::is_same<T, U>::value)>
static constexpr Underlying<T> start_value(U first) {
return static_cast<Underlying<T>>(first);
}

// T not deduced to verify argument is of expected type.
template<typename T, typename U, ENABLE_IF(std::is_same<T, U>::value)>
static constexpr Underlying<T> end_value(U last) {
Underlying<T> value = static_cast<Underlying<T>>(last);
assert(value < std::numeric_limits<Underlying<T>>::max(), "end value overflow");
return static_cast<Underlying<T>>(value + 1);
}
};

// Specialize EnumeratorRange<T>. Start and End must be constant expressions
// whose value is convertible to the underlying type of T. They provide the
// values of the required _start and _end members respectively.
#define ENUMERATOR_VALUE_RANGE(T, Start, End) \
template<> struct EnumeratorRange<T> { \
static constexpr EnumeratorRangeImpl::Underlying<T> _start{Start}; \
static constexpr EnumeratorRangeImpl::Underlying<T> _end{End}; \
};

// A helper class for EnumIterator, computing some additional information the
// iterator uses, based on T and EnumeratorRange.
// Specialize EnumeratorRange<T>. First and Last must be constant expressions
// of type T. They determine the values of the required _start and _end members
// respectively. _start is the underlying value of First. _end is the underlying
// value of Last, plus one.
#define ENUMERATOR_RANGE(T, First, Last) \
ENUMERATOR_VALUE_RANGE(T, \
EnumeratorRangeImpl::start_value<T>(First), \
EnumeratorRangeImpl::end_value<T>(Last));

// A helper class for EnumRange and EnumIterator, computing some
// additional information based on T and EnumeratorRange<T>.
template<typename T>
class EnumIterationTraits : AllStatic {
using RangeType = EnumeratorRange<T>;
Expand All @@ -96,21 +128,20 @@ class EnumIterationTraits : AllStatic {
// The underlying type for T.
using Underlying = std::underlying_type_t<T>;

// The first enumerator of T.
static constexpr T _first = RangeType::_first;
// The value of the first enumerator of T.
static constexpr Underlying _start = RangeType::_start;

// The last enumerator of T.
static constexpr T _last = RangeType::_last;
// The one-past-the-end value for T.
static constexpr Underlying _end = RangeType::_end;

static_assert(static_cast<Underlying>(_last) <
std::numeric_limits<Underlying>::max(),
"No one-past-the-end value for enum");
// The first enumerator of T.
static constexpr T _first = static_cast<T>(_start);

// The value of the first enumerator of T.
static constexpr Underlying _start = static_cast<Underlying>(_first);
// The last enumerator of T.
static constexpr T _last = static_cast<T>(_end - 1);

// The one-past-the-end value for T.
static constexpr Underlying _end = static_cast<Underlying>(_last) + 1;
static_assert(_start != _end, "empty range");
static_assert(_start <= _end, "invalid range"); // <= so only one failure when ==.
};

template<typename T>
Expand All @@ -125,6 +156,8 @@ class EnumIterator {
}

public:
using EnumType = T;

// Return a beyond-the-end iterator.
constexpr EnumIterator() : _value(Traits::_end) {}

Expand Down Expand Up @@ -180,6 +213,7 @@ class EnumRange {
Underlying _end;

public:
using EnumType = T;
using Iterator = EnumIterator<T>;

// Default constructor gives the full range.
Expand Down Expand Up @@ -214,6 +248,17 @@ class EnumRange {
constexpr size_t size() const {
return static_cast<size_t>(_end - _start); // _end is exclusive
}

constexpr T first() const { return static_cast<T>(_start); }
constexpr T last() const { return static_cast<T>(_end - 1); }

// Convert value to a zero-based index into the range [first(), last()].
// precondition: first() <= value && value <= last()
constexpr size_t index(T value) const {
assert(first() <= value, "out of bounds");
assert(value <= last(), "out of bounds");
return static_cast<size_t>(static_cast<Underlying>(value) - _start);
}
};

#endif // SHARE_UTILITIES_ENUMITERATOR_HPP
112 changes: 112 additions & 0 deletions test/hotspot/gtest/utilities/test_enumIterator.cpp
@@ -0,0 +1,112 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

#include "precompiled.hpp"
#include "utilities/enumIterator.hpp"
#include <type_traits>
#include "unittest.hpp"

enum class ExplicitTest : int { value1, value2, value3 };
ENUMERATOR_RANGE(ExplicitTest, ExplicitTest::value1, ExplicitTest::value3);
constexpr int explicit_start = 0;
constexpr int explicit_end = 3;

enum class ImplicitTest : int {};
ENUMERATOR_VALUE_RANGE(ImplicitTest, 5, 10);
constexpr int implicit_start = 5;
constexpr int implicit_end = 10;

TEST(TestEnumIterator, explicit_full_range) {
using Range = EnumRange<ExplicitTest>;
constexpr Range range{};
EXPECT_TRUE((std::is_same<ExplicitTest, Range::EnumType>::value));
EXPECT_EQ(size_t(explicit_end - explicit_start), range.size());
EXPECT_EQ(ExplicitTest::value1, range.first());
EXPECT_EQ(ExplicitTest::value3, range.last());
EXPECT_EQ(size_t(1), range.index(ExplicitTest::value2));
}

TEST(TestEnumIterator, explicit_partial_range) {
using Range = EnumRange<ExplicitTest>;
constexpr Range range{ExplicitTest::value2};
EXPECT_TRUE((std::is_same<ExplicitTest, Range::EnumType>::value));
EXPECT_EQ(size_t(explicit_end - (explicit_start + 1)), range.size());
EXPECT_EQ(ExplicitTest::value2, range.first());
EXPECT_EQ(ExplicitTest::value3, range.last());
EXPECT_EQ(size_t(0), range.index(ExplicitTest::value2));
}

TEST(TestEnumIterator, implicit_full_range) {
using Range = EnumRange<ImplicitTest>;
constexpr Range range{};
EXPECT_TRUE((std::is_same<ImplicitTest, Range::EnumType>::value));
EXPECT_EQ(size_t(implicit_end - implicit_start), range.size());
EXPECT_EQ(static_cast<ImplicitTest>(implicit_start), range.first());
EXPECT_EQ(static_cast<ImplicitTest>(implicit_end - 1), range.last());
EXPECT_EQ(size_t(2), range.index(static_cast<ImplicitTest>(implicit_start + 2)));
}

TEST(TestEnumIterator, implicit_partial_range) {
using Range = EnumRange<ImplicitTest>;
constexpr Range range{static_cast<ImplicitTest>(implicit_start + 2)};
EXPECT_TRUE((std::is_same<ImplicitTest, Range::EnumType>::value));
EXPECT_EQ(size_t(implicit_end - (implicit_start + 2)), range.size());
EXPECT_EQ(static_cast<ImplicitTest>(implicit_start + 2), range.first());
EXPECT_EQ(static_cast<ImplicitTest>(implicit_end - 1), range.last());
EXPECT_EQ(size_t(1), range.index(static_cast<ImplicitTest>(implicit_start + 3)));
}

TEST(TestEnumIterator, explict_iterator) {
using Range = EnumRange<ExplicitTest>;
using Iterator = EnumIterator<ExplicitTest>;
constexpr Range range{};
EXPECT_EQ(range.first(), *range.begin());
EXPECT_EQ(Iterator(range.first()), range.begin());
EnumIterator<ExplicitTest> it = range.begin();
++it;
EXPECT_EQ(ExplicitTest::value2, *it);
it = range.begin();
for (int i = explicit_start; i < explicit_end; ++i, ++it) {
ExplicitTest value = static_cast<ExplicitTest>(i);
EXPECT_EQ(value, *it);
EXPECT_EQ(Iterator(value), it);
EXPECT_EQ(size_t(i - explicit_start), range.index(value));
}
EXPECT_EQ(it, range.end());
}

TEST(TestEnumIterator, implicit_iterator) {
using Range = EnumRange<ImplicitTest>;
using Iterator = EnumIterator<ImplicitTest>;
constexpr Range range{};
EXPECT_EQ(range.first(), *range.begin());
EXPECT_EQ(Iterator(range.first()), range.begin());
EnumIterator<ImplicitTest> it = range.begin();
for (int i = implicit_start; i < implicit_end; ++i, ++it) {
ImplicitTest value = static_cast<ImplicitTest>(i);
EXPECT_EQ(value, *it);
EXPECT_EQ(Iterator(value), it);
EXPECT_EQ(size_t(i - implicit_start), range.index(value));
}
EXPECT_EQ(it, range.end());
}

1 comment on commit 3932527

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.