mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 21:00:13 +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>
135 lines
4.5 KiB
C++
135 lines
4.5 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_OSTREAM_H_
|
|
#define CARBON_COMMON_OSTREAM_H_
|
|
|
|
// Libraries should include this header instead of raw_ostream.
|
|
|
|
#include <compare>
|
|
#include <concepts>
|
|
#include <ostream>
|
|
#include <type_traits>
|
|
|
|
#include "llvm/Support/Compiler.h"
|
|
#include "llvm/Support/raw_os_ostream.h"
|
|
#include "llvm/Support/raw_ostream.h" // IWYU pragma: export
|
|
|
|
namespace Carbon {
|
|
|
|
// CRTP base class for printable types. Derived classes (DerivedT) must
|
|
// implement:
|
|
// - auto Print(llvm::raw_ostream& out) const -> void
|
|
template <typename DerivedT>
|
|
// NOLINTNEXTLINE(bugprone-crtp-constructor-accessibility)
|
|
class Printable {
|
|
// Comparisons of the base class itself, which is empty and so always compares
|
|
// equal, allowing derived classes to default their own comparison operators.
|
|
//
|
|
// These are templated so that they are only used when the types of the
|
|
// arguments are exactly `Printable`, rather than a derived class, and are
|
|
// hidden friends so that they aren't candidates for unrelated comparisons.
|
|
template <typename T>
|
|
requires std::same_as<T, Printable>
|
|
friend constexpr auto operator==(const T& /*lhs*/, const T& /*rhs*/) noexcept
|
|
-> bool {
|
|
return true;
|
|
}
|
|
template <typename T>
|
|
requires std::same_as<T, Printable>
|
|
friend constexpr auto operator<=>(const T& /*lhs*/, const T& /*rhs*/) noexcept
|
|
-> std::strong_ordering {
|
|
return std::strong_ordering::equal;
|
|
}
|
|
|
|
// Supports printing to llvm::raw_ostream.
|
|
friend auto operator<<(llvm::raw_ostream& out, const DerivedT& obj)
|
|
-> llvm::raw_ostream& {
|
|
obj.Print(out);
|
|
return out;
|
|
}
|
|
|
|
// Supports printing to std::ostream.
|
|
friend auto operator<<(std::ostream& out, const DerivedT& obj)
|
|
-> std::ostream& {
|
|
llvm::raw_os_ostream raw_os(out);
|
|
obj.Print(raw_os);
|
|
return out;
|
|
}
|
|
|
|
// Allows GoogleTest and GoogleMock to print pointers by dereferencing them.
|
|
// This is important to allow automatic printing of arguments of mocked
|
|
// APIs.
|
|
friend auto PrintTo(DerivedT* p, std::ostream* out) -> void {
|
|
*out << static_cast<const void*>(p);
|
|
// Also print the object if non-null.
|
|
if (p) {
|
|
*out << " pointing to " << *p;
|
|
}
|
|
}
|
|
};
|
|
|
|
// Helper class for printing strings with escapes.
|
|
//
|
|
// For example:
|
|
// stream << FormatEscaped(str);
|
|
// Is equivalent to:
|
|
// stream.write_escaped(str);
|
|
class FormatEscaped : public Printable<FormatEscaped> {
|
|
public:
|
|
explicit FormatEscaped(llvm::StringRef str, bool use_hex_escapes = false)
|
|
: str_(str), use_hex_escapes_(use_hex_escapes) {}
|
|
|
|
auto Print(llvm::raw_ostream& out) const -> void {
|
|
out.write_escaped(str_, use_hex_escapes_);
|
|
}
|
|
|
|
private:
|
|
llvm::StringRef str_;
|
|
bool use_hex_escapes_;
|
|
};
|
|
|
|
// Returns the result of printing the value.
|
|
template <typename T>
|
|
requires std::derived_from<T, Printable<T>>
|
|
inline auto PrintToString(const T& val) -> std::string {
|
|
std::string str;
|
|
llvm::raw_string_ostream stream(str);
|
|
stream << val;
|
|
return str;
|
|
}
|
|
|
|
} // namespace Carbon
|
|
|
|
namespace llvm {
|
|
|
|
// Injects an `operator<<` overload into the `llvm` namespace which detects LLVM
|
|
// types with `raw_ostream` overloads and uses that to map to a `std::ostream`
|
|
// overload. This allows LLVM types to be printed to `std::ostream` via their
|
|
// `raw_ostream` operator overloads, which is needed both for logging and
|
|
// testing.
|
|
//
|
|
// To make this overload be unusually low priority, it is designed to take even
|
|
// the `std::ostream` parameter as a template, and SFINAE disable itself unless
|
|
// that template parameter is derived from `std::ostream`. This ensures that an
|
|
// *explicit* operator will be preferred when provided. Some LLVM types may have
|
|
// this, and so we want to prioritize accordingly.
|
|
//
|
|
// It would be slightly cleaner for LLVM itself to provide this overload in
|
|
// `raw_os_ostream.h` so that we wouldn't need to inject into LLVM's namespace,
|
|
// but supporting `std::ostream` isn't a priority for LLVM so we handle it
|
|
// locally instead.
|
|
template <typename StreamT, typename ClassT>
|
|
requires std::derived_from<std::decay_t<StreamT>, std::ostream> &&
|
|
(!std::same_as<std::decay_t<ClassT>, raw_ostream>) &&
|
|
requires(raw_ostream& os, const ClassT& value) { os << value; }
|
|
auto operator<<(StreamT& standard_out, const ClassT& value) -> StreamT& {
|
|
raw_os_ostream(standard_out) << value;
|
|
return standard_out;
|
|
}
|
|
|
|
} // namespace llvm
|
|
|
|
#endif // CARBON_COMMON_OSTREAM_H_
|