mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 19:30:12 +01:00
A defaulted `operator==` or `operator<=>` compares every base class subobject, so children of `Printable` couldn't default their comparisons: `Printable` had no comparison operators of its own, which made the defaulted operator implicitly deleted. Children that want member-wise comparison had to write it out by hand instead. `Printable` is empty, so it now provides comparisons that always compare equal. Its operands are constrained template parameter rather than `const Printable&` so that they're only viable for comparing the base class subobjects themselves. An overload taking `const Printable&` would also be viable when comparing two `DerivedT` objects by converting them to the base class, and would then both make children that provide no comparison silently compare equal and displace the comparisons of children that provide them through a conversion of their own, as `EnumBase` does. Some hand-written comparisons stay, for reasons unrelated to `Printable`: using `= default` would change their meaning. Adds `common/ostream_test.cpp`, which covers both the member and friend forms of defaulting, the resulting comparison categories, and both of the hazards above. Assisted-by: Claude Code --------- Co-authored-by: Richard Smith <richard@metafoo.co.uk>
157 lines
4.9 KiB
C++
157 lines
4.9 KiB
C++
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
|
// Exceptions. See /LICENSE for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
|
|
#ifndef CARBON_COMMON_RAW_HASHTABLE_TEST_HELPERS_H_
|
|
#define CARBON_COMMON_RAW_HASHTABLE_TEST_HELPERS_H_
|
|
|
|
#include <compare>
|
|
|
|
#include "common/check.h"
|
|
#include "common/hashing.h"
|
|
#include "common/hashtable_key_context.h"
|
|
#include "common/ostream.h"
|
|
|
|
namespace Carbon::RawHashtable {
|
|
|
|
// Non-trivial type for testing.
|
|
struct TestData : Printable<TestData> {
|
|
int value;
|
|
|
|
// NOLINTNEXTLINE: google-explicit-constructor
|
|
TestData(int v) : value(v) { CARBON_CHECK(value >= 0); }
|
|
~TestData() {
|
|
CARBON_CHECK(value >= 0);
|
|
value = -1;
|
|
}
|
|
TestData(const TestData& other) : TestData(other.value) {}
|
|
TestData(TestData&& other) noexcept : TestData(other.value) {
|
|
other.value = 0;
|
|
}
|
|
auto Print(llvm::raw_ostream& out) const -> void { out << value; }
|
|
|
|
friend auto operator==(TestData lhs, TestData rhs) -> bool {
|
|
return lhs.value == rhs.value;
|
|
}
|
|
|
|
friend auto operator<=>(TestData lhs, TestData rhs) -> std::strong_ordering {
|
|
return lhs.value <=> rhs.value;
|
|
}
|
|
|
|
friend auto CarbonHashValue(TestData data, uint64_t seed) -> HashCode {
|
|
return Carbon::HashValue(data.value, seed);
|
|
}
|
|
};
|
|
|
|
static_assert(std::is_copy_constructible_v<TestData>);
|
|
|
|
inline auto CarbonHashtableEq(int lhs, TestData rhs) -> bool {
|
|
return lhs == rhs;
|
|
}
|
|
|
|
// Non-trivial type for testing.
|
|
struct MoveOnlyTestData : Printable<TestData> {
|
|
int value;
|
|
|
|
// NOLINTNEXTLINE: google-explicit-constructor
|
|
MoveOnlyTestData(int v) : value(v) { CARBON_CHECK(value >= 0); }
|
|
~MoveOnlyTestData() {
|
|
CARBON_CHECK(value >= 0);
|
|
value = -1;
|
|
}
|
|
MoveOnlyTestData(MoveOnlyTestData&& other) noexcept
|
|
: MoveOnlyTestData(other.value) {
|
|
other.value = 0;
|
|
}
|
|
auto operator=(MoveOnlyTestData&& other) noexcept -> MoveOnlyTestData& {
|
|
value = other.value;
|
|
other.value = 0;
|
|
return *this;
|
|
}
|
|
auto Print(llvm::raw_ostream& out) const -> void { out << value; }
|
|
|
|
friend auto operator<=>(const MoveOnlyTestData& lhs,
|
|
const MoveOnlyTestData& rhs)
|
|
-> std::strong_ordering = default;
|
|
|
|
friend auto CarbonHashValue(const MoveOnlyTestData& data, uint64_t seed)
|
|
-> HashCode {
|
|
return Carbon::HashValue(data.value, seed);
|
|
}
|
|
};
|
|
|
|
static_assert(!std::is_copy_constructible_v<MoveOnlyTestData>);
|
|
static_assert(std::is_move_constructible_v<MoveOnlyTestData>);
|
|
|
|
inline auto CarbonHashtableEq(int lhs, const MoveOnlyTestData& rhs) -> bool {
|
|
return lhs == rhs;
|
|
}
|
|
|
|
// Test stateless key context that produces different hashes from normal.
|
|
// Changing the hash values should result in test failures if the context ever
|
|
// fails to be used.
|
|
struct TestKeyContext : DefaultKeyContext {
|
|
template <typename KeyT>
|
|
auto HashKey(const KeyT& key, uint64_t seed) const -> HashCode {
|
|
Hasher hash(seed);
|
|
// Inject some other data to the hash.
|
|
hash.HashRaw(42);
|
|
hash.HashRaw(HashValue(key));
|
|
return static_cast<HashCode>(hash);
|
|
}
|
|
};
|
|
|
|
// Hostile fixed hashing key context used for stress testing. Allows control
|
|
// over which parts of the hash will be forced to collide, and the values they
|
|
// are coerced to. Note that this relies on implementation details and internals
|
|
// of `HashCode`.
|
|
template <int TagBits, bool FixIndexBits, bool FixTagBits, uint64_t FixedVal>
|
|
struct FixedHashKeyContext : DefaultKeyContext {
|
|
template <typename KeyT>
|
|
auto HashKey(const KeyT& key, uint64_t seed) const -> HashCode {
|
|
HashCode original_hash = HashValue(key, seed);
|
|
auto raw_hash = static_cast<uint64_t>(original_hash);
|
|
|
|
constexpr uint64_t TagMask = (1U << TagBits) - 1;
|
|
if (FixIndexBits) {
|
|
raw_hash &= TagMask;
|
|
raw_hash |= FixedVal << TagBits;
|
|
CARBON_DCHECK(HashCode(raw_hash).ExtractIndexAndTag<TagBits>().first ==
|
|
(FixedVal & (~static_cast<uint64_t>(0) >> TagBits)));
|
|
}
|
|
if (FixTagBits) {
|
|
raw_hash &= ~TagMask;
|
|
raw_hash |= FixedVal & TagMask;
|
|
CARBON_DCHECK(HashCode(raw_hash).ExtractIndexAndTag<TagBits>().second ==
|
|
(FixedVal & TagMask));
|
|
}
|
|
return HashCode(raw_hash);
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
class IndexKeyContext : public TranslatingKeyContext<IndexKeyContext<T>> {
|
|
using Base = TranslatingKeyContext<IndexKeyContext>;
|
|
|
|
public:
|
|
explicit IndexKeyContext(llvm::ArrayRef<T> array) : array_(array) {}
|
|
|
|
auto TranslateKey(ssize_t index) const -> const T& { return array_[index]; }
|
|
|
|
// Override the CRTP approach when we have two indices as we can optimize that
|
|
// approach.
|
|
using Base::KeyEq;
|
|
auto KeyEq(ssize_t lhs_index, ssize_t rhs_index) const -> bool {
|
|
// No need to compare the elements, if the indices are equal, the values
|
|
// must be.
|
|
return lhs_index == rhs_index;
|
|
}
|
|
|
|
private:
|
|
llvm::ArrayRef<T> array_;
|
|
};
|
|
|
|
} // namespace Carbon::RawHashtable
|
|
|
|
#endif // CARBON_COMMON_RAW_HASHTABLE_TEST_HELPERS_H_
|