Summary
Two correctness defects in include/set.h. Both are currently masked by the test suite (one via a compiler-conditional expectation, one because the buggy overload is never instantiated) rather than fixed.
1. set::min() / max() ignore the set's comparator (and are O(n))
Location: include/set.h:607-635
[[nodiscard]] fcpp::optional_t<TKey> min() const
{
const auto& it = std::min_element(begin(), end());
if (it != end()) {
return *it;
}
return fcpp::optional_t<TKey>();
}
std::min_element / std::max_element compare elements with operator< on TKey, not with the set's TCompare.
Consequences
- Wrong result for any set instantiated with a custom comparator. The set is stored ordered by
TCompare, but min()/max() re-rank by operator<, so they can return the wrong element.
- Fails to compile for key types that provide
TCompare but not operator<.
- O(n) where it should be O(1): a
std::set is already ordered, so the minimum is *begin() and the maximum is *std::prev(end()).
Evidence — the test papers over this instead of fixing it (tests/set_test.cc:289):
TEST(SetTest, MinCustomType)
{
const set<person, person_comparator> persons({
person(15, "Jake"), person(18, "Jannet"),
person(25, "Kate"), person(62, "Bob")
});
const auto minimum = persons.min();
#if defined(__clang__)
EXPECT_EQ(person(18, "Jannet"), minimum.value());
#else
EXPECT_EQ(person(62, "Bob"), minimum.value()); // different answer per compiler
#endif
}
The fact that the expected value differs between clang and gcc is a direct symptom of the bug: the answer depends on operator< and iteration order rather than the set's actual ordering.
Suggested fix
[[nodiscard]] fcpp::optional_t<TKey> min() const
{
if (m_set.empty()) {
return fcpp::optional_t<TKey>();
}
return *begin(); // ordered by TCompare, O(1)
}
[[nodiscard]] fcpp::optional_t<TKey> max() const
{
if (m_set.empty()) {
return fcpp::optional_t<TKey>();
}
return *std::prev(end()); // O(1)
}
This is deterministic, respects the custom comparator, is O(1), and allows the #if defined(__clang__) branch in SetTest.MinCustomType (and the analogous max test) to be removed.
2. Non-const set::operator[] does not compile
Location: include/set.h:1102-1116
TKey operator[](size_t index)
{
assert_smaller_size(index);
#ifdef CPP17_AVAILABLE
auto it = std::advance(begin(), index); // std::advance returns void
return *it;
#else
...
#endif
}
std::advance returns void, so auto it = std::advance(...) is ill-formed. The const overload (set.h:1121-1136) does it correctly:
auto it = begin();
std::advance(it, index);
return *it;
Why it's currently hidden: every call site in the tests uses operator[] on a const set& (e.g. test_contents(const set<int>& set) at tests/set_test.cc:37), so the non-const overload is never instantiated. Any use such as:
fcpp::set<int> s({1, 2, 3});
auto x = s[0]; // selects the non-const overload -> compile error
would fail to build under C++17.
Suggested fix: mirror the const overload:
TKey operator[](size_t index)
{
assert_smaller_size(index);
auto it = begin();
std::advance(it, index); // works for both C++11 and C++17 paths
return *it;
}
A regression test that subscripts a non-const set would prevent this from regressing.
Summary
Two correctness defects in
include/set.h. Both are currently masked by the test suite (one via a compiler-conditional expectation, one because the buggy overload is never instantiated) rather than fixed.1.
set::min()/max()ignore the set's comparator (and are O(n))Location:
include/set.h:607-635std::min_element/std::max_elementcompare elements withoperator<onTKey, not with the set'sTCompare.Consequences
TCompare, butmin()/max()re-rank byoperator<, so they can return the wrong element.TComparebut notoperator<.std::setis already ordered, so the minimum is*begin()and the maximum is*std::prev(end()).Evidence — the test papers over this instead of fixing it (
tests/set_test.cc:289):The fact that the expected value differs between clang and gcc is a direct symptom of the bug: the answer depends on
operator<and iteration order rather than the set's actual ordering.Suggested fix
This is deterministic, respects the custom comparator, is O(1), and allows the
#if defined(__clang__)branch inSetTest.MinCustomType(and the analogous max test) to be removed.2. Non-const
set::operator[]does not compileLocation:
include/set.h:1102-1116std::advancereturnsvoid, soauto it = std::advance(...)is ill-formed. The const overload (set.h:1121-1136) does it correctly:Why it's currently hidden: every call site in the tests uses
operator[]on aconst set&(e.g.test_contents(const set<int>& set)attests/set_test.cc:37), so the non-const overload is never instantiated. Any use such as:would fail to build under C++17.
Suggested fix: mirror the const overload:
A regression test that subscripts a non-const set would prevent this from regressing.