Skip to content

Commit 0ebec4c

Browse files
jkaliasclaude
andauthored
Fix set::min()/max() comparator bug and non-const operator[] (#32)
* 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. * CI: pin Windows runner to windows-2022 for VS 2022 generator GitHub's windows-latest image no longer provides the "Visual Studio 17 2022" generator, so CMake configure failed with "could not find any instance of Visual Studio" before compiling. Pin the Windows matrix entries to windows-2022. --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 764adeb commit 0ebec4c

3 files changed

Lines changed: 44 additions & 19 deletions

File tree

.github/workflows/cmake.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
config:
2323
- {
2424
name: "Windows Latest MSVC (C++11)",
25-
os: windows-latest,
25+
os: windows-2022,
2626
build_type: "Debug",
2727
cc: "cl",
2828
cxx: "cl",
@@ -31,7 +31,7 @@ jobs:
3131
}
3232
- {
3333
name: "Windows Latest MSVC (C++17)",
34-
os: windows-latest,
34+
os: windows-2022,
3535
build_type: "Debug",
3636
cc: "cl",
3737
cxx: "cl",

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)