Switch //common to use C++20 concepts. (#3665)

This removes the use of `enable_if` and tries to adopt concepts instead
of type traits when available.

The `ostream.h` change is a bit subtle as it adds a restriction not
previously in place -- that the stream is *contvertible* to
`std::ostream` as well as having it as a base class. This seems to match
the intent of the code.

The `hashing.h` code adds an implementation detail concept, and so I've
also clarified that the dispatch namespace is an internal one that isn't
part of the public API.
This commit is contained in:
Chandler Carruth
2024-01-30 18:14:07 +00:00
committed by GitHub
parent 79ac4c953a
commit 2e236759ca
3 changed files with 44 additions and 42 deletions
+34 -31
View File
@@ -5,6 +5,7 @@
#ifndef CARBON_COMMON_HASHING_H_
#define CARBON_COMMON_HASHING_H_
#include <concepts>
#include <string>
#include <tuple>
#include <type_traits>
@@ -239,8 +240,8 @@ class Hasher {
// This can be directly used for simple users combining some aggregation of
// objects. However, when possible, prefer the variadic version below for
// aggregating several primitive types into a hash.
template <typename T, typename = std::enable_if_t<
std::has_unique_object_representations_v<T>>>
template <typename T>
requires std::has_unique_object_representations_v<T>
auto Hash(const T& value) -> void;
// Incorporates a variable number of objects into the `hasher`s state in a
@@ -256,9 +257,8 @@ class Hasher {
// aggregations of data in this way is rarely results in effectively
// high-performance hash table data structures and so should generally be
// avoided.
template <typename... Ts,
typename = std::enable_if_t<
(... && std::has_unique_object_representations_v<Ts>)>>
template <typename... Ts>
requires(... && std::has_unique_object_representations_v<Ts>)
auto Hash(const Ts&... value) -> void;
// Simpler and more primitive functions to incorporate state represented in
@@ -311,9 +311,8 @@ class Hasher {
// Reads the underlying object representation of a type into a 64-bit integer
// efficiently. Only supports types with unique object representation and at
// most 8-bytes large. This is typically used to read primitive types.
template <typename T,
typename = std::enable_if_t<
std::has_unique_object_representations_v<T> && sizeof(T) <= 8>>
template <typename T>
requires std::has_unique_object_representations_v<T> && (sizeof(T) <= 8)
static auto ReadSmall(const T& value) -> uint64_t;
// The core of the hash algorithm is this mix function. The specific
@@ -429,7 +428,10 @@ class Hasher {
// A dedicated namespace for `CarbonHashValue` overloads that are not found by
// ADL with their associated types. For example, primitive type overloads or
// overloads for types in LLVM's libraries.
namespace HashDispatch {
//
// Note that these are internal implementation details and **not** part of the
// public API. They should not be used directly by client code.
namespace InternalHashDispatch {
inline auto CarbonHashValue(llvm::ArrayRef<std::byte> bytes, uint64_t seed)
-> HashCode {
@@ -482,31 +484,30 @@ template <typename T>
inline auto MapNullPtrToVoidPtr(const T& value) -> const T& {
// This overload should never be selected for `std::nullptr_t`, so
// static_assert to get some better compiler error messages.
static_assert(!std::is_same_v<T, std::nullptr_t>);
static_assert(!std::same_as<T, std::nullptr_t>);
return value;
}
inline auto MapNullPtrToVoidPtr(std::nullptr_t /*value*/) -> const void* {
return nullptr;
}
// Predicate to be used in conjunction with a `nullptr` mapping routine like the
// above.
// Implementation detail predicate to be used in conjunction with a `nullptr`
// mapping routine like the above.
template <typename T>
constexpr bool NullPtrOrHasUniqueObjectRepresentations =
std::is_same_v<T, std::nullptr_t> ||
concept NullPtrOrHasUniqueObjectRepresentations =
std::same_as<T, std::nullptr_t> ||
std::has_unique_object_representations_v<T>;
template <typename T, typename = std::enable_if_t<
NullPtrOrHasUniqueObjectRepresentations<T>>>
template <typename T>
requires NullPtrOrHasUniqueObjectRepresentations<T>
inline auto CarbonHashValue(const T& value, uint64_t seed) -> HashCode {
Hasher hasher(seed);
hasher.Hash(MapNullPtrToVoidPtr(value));
return static_cast<HashCode>(hasher);
}
template <typename... Ts,
typename = std::enable_if_t<
(... && NullPtrOrHasUniqueObjectRepresentations<Ts>)>>
template <typename... Ts>
requires(... && NullPtrOrHasUniqueObjectRepresentations<Ts>)
inline auto CarbonHashValue(const std::tuple<Ts...>& value, uint64_t seed)
-> HashCode {
Hasher hasher(seed);
@@ -516,18 +517,17 @@ inline auto CarbonHashValue(const std::tuple<Ts...>& value, uint64_t seed)
return static_cast<HashCode>(hasher);
}
template <typename T, typename U,
typename = std::enable_if_t<
NullPtrOrHasUniqueObjectRepresentations<T> &&
NullPtrOrHasUniqueObjectRepresentations<U> &&
sizeof(T) <= sizeof(uint64_t) && sizeof(U) <= sizeof(uint64_t)>>
template <typename T, typename U>
requires NullPtrOrHasUniqueObjectRepresentations<T> &&
NullPtrOrHasUniqueObjectRepresentations<U> &&
(sizeof(T) <= sizeof(uint64_t) && sizeof(U) <= sizeof(uint64_t))
inline auto CarbonHashValue(const std::pair<T, U>& value, uint64_t seed)
-> HashCode {
return CarbonHashValue(std::tuple(value.first, value.second), seed);
}
template <typename T, typename = std::enable_if_t<
std::has_unique_object_representations_v<T>>>
template <typename T>
requires std::has_unique_object_representations_v<T>
inline auto CarbonHashValue(llvm::ArrayRef<T> objs, uint64_t seed) -> HashCode {
return CarbonHashValue(
llvm::ArrayRef(reinterpret_cast<const std::byte*>(objs.data()),
@@ -542,11 +542,11 @@ inline auto DispatchImpl(const T& value, uint64_t seed) -> HashCode {
return CarbonHashValue(value, seed);
}
} // namespace HashDispatch
} // namespace InternalHashDispatch
template <typename T>
inline auto HashValue(const T& value, uint64_t seed) -> HashCode {
return HashDispatch::DispatchImpl(value, seed);
return InternalHashDispatch::DispatchImpl(value, seed);
}
template <typename T>
@@ -683,7 +683,8 @@ inline auto Hasher::HashDense(uint64_t data0, uint64_t data1) -> void {
Mix(data0 ^ StaticRandomData[1], data1 ^ StaticRandomData[3] ^ buffer);
}
template <typename T, typename /*enable_if*/>
template <typename T>
requires std::has_unique_object_representations_v<T> && (sizeof(T) <= 8)
inline auto Hasher::ReadSmall(const T& value) -> uint64_t {
const auto* storage = reinterpret_cast<const std::byte*>(&value);
if constexpr (sizeof(T) == 1) {
@@ -706,7 +707,8 @@ inline auto Hasher::ReadSmall(const T& value) -> uint64_t {
}
}
template <typename T, typename /*enable_if*/>
template <typename T>
requires std::has_unique_object_representations_v<T>
inline auto Hasher::Hash(const T& value) -> void {
if constexpr (sizeof(T) <= 8) {
// For types size 8-bytes and smaller directly being hashed (as opposed to
@@ -748,7 +750,8 @@ inline auto Hasher::Hash(const T& value) -> void {
HashSizedBytesLarge(llvm::ArrayRef<std::byte>(data_ptr, sizeof(T)));
}
template <typename... Ts, typename /*enable_if*/>
template <typename... Ts>
requires(... && std::has_unique_object_representations_v<Ts>)
inline auto Hasher::Hash(const Ts&... value) -> void {
if constexpr (sizeof...(Ts) == 0) {
buffer ^= StaticRandomData[0];
+5 -5
View File
@@ -7,7 +7,7 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <type_traits>
#include <concepts>
#include "llvm/ADT/Sequence.h"
#include "llvm/ADT/StringExtras.h"
@@ -339,7 +339,8 @@ auto PrintFullWidthHex(llvm::raw_ostream& os, T value) {
static_cast<uint64_t>(value));
}
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
template <typename T>
requires std::integral<T>
auto operator<<(llvm::raw_ostream& os, HashedValue<T> hv)
-> llvm::raw_ostream& {
os << "hash " << hv.hash << " for value ";
@@ -347,9 +348,8 @@ auto operator<<(llvm::raw_ostream& os, HashedValue<T> hv)
return os;
}
template <typename T, typename U,
typename = std::enable_if_t<std::is_integral_v<T>>,
typename = std::enable_if_t<std::is_integral_v<U>>>
template <typename T, typename U>
requires std::integral<T> && std::integral<U>
auto operator<<(llvm::raw_ostream& os, HashedValue<std::pair<T, U>> hv)
-> llvm::raw_ostream& {
os << "hash " << hv.hash << " for pair of ";
+5 -6
View File
@@ -5,6 +5,7 @@
#ifndef CARBON_COMMON_OSTREAM_H_
#define CARBON_COMMON_OSTREAM_H_
#include <concepts>
#include <ostream>
#include <type_traits>
@@ -72,7 +73,7 @@ namespace llvm {
//
// 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 matches `std::ostream`. This ensures that an
// 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.
//
@@ -80,11 +81,9 @@ namespace llvm {
// `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,
typename = std::enable_if_t<
std::is_base_of_v<std::ostream, std::decay_t<StreamT>>>,
typename = std::enable_if_t<
!std::is_same_v<std::decay_t<ClassT>, raw_ostream>>>
template <typename StreamT, typename ClassT>
requires std::derived_from<std::decay_t<StreamT>, std::ostream> &&
(!std::same_as<std::decay_t<ClassT>, raw_ostream>)
auto operator<<(StreamT& standard_out, const ClassT& value) -> StreamT& {
raw_os_ostream(standard_out) << value;
return standard_out;