Skip to content

Commit dc2d83a

Browse files
committed
Fix set::min()/max() comparator bug and non-const operator[] (#28)
set::min()/max() used std::min_element/std::max_element, which rank by operator< instead of the set's TCompare. For sets with a custom comparator this returned the wrong element, required operator< even when a comparator was supplied, and was O(n). They now return *begin() / *std::prev(end()), which respects the comparator and is O(1). The non-const set::operator[] did `auto it = std::advance(begin(), index)`, but std::advance returns void, so the overload failed to compile whenever instantiated (subscripting a non-const set). It now advances an iterator in place, matching the const overload, and works on both C++11 and C++17. Added tests: min/max with a comparator that differs from operator< (descending int comparator), and non-const subscripting.
1 parent 764adeb commit dc2d83a

2 files changed

Lines changed: 42 additions & 17 deletions

File tree

include/set.h

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -606,11 +606,13 @@ namespace fcpp {
606606
// minimum.value() -> 1
607607
[[nodiscard]] fcpp::optional_t<TKey> min() const
608608
{
609-
const auto& it = std::min_element(begin(), end());
610-
if (it != end()) {
611-
return *it;
609+
if (m_set.empty()) {
610+
return fcpp::optional_t<TKey>();
612611
}
613-
return fcpp::optional_t<TKey>();
612+
// The set is already ordered by TCompare, so the minimum is the first
613+
// element (O(1)). Using std::min_element would re-rank by operator<,
614+
// ignoring the custom comparator and costing O(n).
615+
return *begin();
614616
}
615617

616618
// Returns the maximum key in the set, if it's not empty.
@@ -627,11 +629,13 @@ namespace fcpp {
627629
// maximum.value() -> 8
628630
[[nodiscard]] fcpp::optional_t<TKey> max() const
629631
{
630-
const auto& it = std::max_element(begin(), end());
631-
if (it != end()) {
632-
return *it;
632+
if (m_set.empty()) {
633+
return fcpp::optional_t<TKey>();
633634
}
634-
return fcpp::optional_t<TKey>();
635+
// The set is already ordered by TCompare, so the maximum is the last
636+
// element (O(1)). Using std::max_element would re-rank by operator<,
637+
// ignoring the custom comparator and costing O(n).
638+
return *std::prev(end());
635639
}
636640

637641
// Performs the functional `map` algorithm, in which every element of the resulting set is the
@@ -1102,17 +1106,9 @@ namespace fcpp {
11021106
TKey operator[](size_t index)
11031107
{
11041108
assert_smaller_size(index);
1105-
#ifdef CPP17_AVAILABLE
1106-
auto it = std::advance(begin(), index);
1107-
return *it;
1108-
#else
1109-
auto count = 0;
11101109
auto it = begin();
1111-
while (count++ < index) {
1112-
++it;
1113-
}
1110+
std::advance(it, index);
11141111
return *it;
1115-
#endif
11161112
}
11171113

11181114
// Returns a copy of the key at the given sorted position.

tests/set_test.cc

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,35 @@ TEST(SetTest, MaxEmptySet)
332332
EXPECT_FALSE(maximum.has_value());
333333
}
334334

335+
// min()/max() must respect the set's own comparator, not operator<.
336+
// The descending comparator orders ints opposite to operator<, so the
337+
// comparator-smallest element is the largest int, and vice versa.
338+
TEST(SetTest, MinRespectsCustomComparator)
339+
{
340+
const set<int, stateful_descending_int_compare> numbers(
341+
make_stateful_descending_set({1, 4, 2, 5, 8, 3}));
342+
const auto minimum = numbers.min();
343+
EXPECT_TRUE(minimum.has_value());
344+
EXPECT_EQ(8, minimum.value());
345+
}
346+
347+
TEST(SetTest, MaxRespectsCustomComparator)
348+
{
349+
const set<int, stateful_descending_int_compare> numbers(
350+
make_stateful_descending_set({1, 4, 2, 5, 8, 3}));
351+
const auto maximum = numbers.max();
352+
EXPECT_TRUE(maximum.has_value());
353+
EXPECT_EQ(1, maximum.value());
354+
}
355+
356+
TEST(SetTest, NonConstSubscripting)
357+
{
358+
set<int> set_under_test(std::set<int>({1, 5, 3, 3}));
359+
EXPECT_EQ(1, set_under_test[0]);
360+
EXPECT_EQ(3, set_under_test[1]);
361+
EXPECT_EQ(5, set_under_test[2]);
362+
}
363+
335364
TEST(SetTest, Map)
336365
{
337366
const set<int> numbers({4, 1, 3});

0 commit comments

Comments
 (0)