Просмотр исходного кода

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.
Chandler Carruth 2 лет назад
Родитель
Сommit
2e236759ca
3 измененных файлов с 44 добавлено и 42 удалено
  1. 34 31
      common/hashing.h
  2. 5 5
      common/hashing_test.cpp
  3. 5 6
      common/ostream.h

+ 34 - 31
common/hashing.h

@@ -5,6 +5,7 @@
 #ifndef CARBON_COMMON_HASHING_H_
 #ifndef CARBON_COMMON_HASHING_H_
 #define CARBON_COMMON_HASHING_H_
 #define CARBON_COMMON_HASHING_H_
 
 
+#include <concepts>
 #include <string>
 #include <string>
 #include <tuple>
 #include <tuple>
 #include <type_traits>
 #include <type_traits>
@@ -239,8 +240,8 @@ class Hasher {
   // This can be directly used for simple users combining some aggregation of
   // This can be directly used for simple users combining some aggregation of
   // objects. However, when possible, prefer the variadic version below for
   // objects. However, when possible, prefer the variadic version below for
   // aggregating several primitive types into a hash.
   // 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;
   auto Hash(const T& value) -> void;
 
 
   // Incorporates a variable number of objects into the `hasher`s state in a
   // 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
   // aggregations of data in this way is rarely results in effectively
   // high-performance hash table data structures and so should generally be
   // high-performance hash table data structures and so should generally be
   // avoided.
   // 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;
   auto Hash(const Ts&... value) -> void;
 
 
   // Simpler and more primitive functions to incorporate state represented in
   // 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
   // Reads the underlying object representation of a type into a 64-bit integer
   // efficiently. Only supports types with unique object representation and at
   // efficiently. Only supports types with unique object representation and at
   // most 8-bytes large. This is typically used to read primitive types.
   // 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;
   static auto ReadSmall(const T& value) -> uint64_t;
 
 
   // The core of the hash algorithm is this mix function. The specific
   // 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
 // A dedicated namespace for `CarbonHashValue` overloads that are not found by
 // ADL with their associated types. For example, primitive type overloads or
 // ADL with their associated types. For example, primitive type overloads or
 // overloads for types in LLVM's libraries.
 // 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)
 inline auto CarbonHashValue(llvm::ArrayRef<std::byte> bytes, uint64_t seed)
     -> HashCode {
     -> HashCode {
@@ -482,31 +484,30 @@ template <typename T>
 inline auto MapNullPtrToVoidPtr(const T& value) -> const T& {
 inline auto MapNullPtrToVoidPtr(const T& value) -> const T& {
   // This overload should never be selected for `std::nullptr_t`, so
   // This overload should never be selected for `std::nullptr_t`, so
   // static_assert to get some better compiler error messages.
   // 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;
   return value;
 }
 }
 inline auto MapNullPtrToVoidPtr(std::nullptr_t /*value*/) -> const void* {
 inline auto MapNullPtrToVoidPtr(std::nullptr_t /*value*/) -> const void* {
   return nullptr;
   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>
 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>;
     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 {
 inline auto CarbonHashValue(const T& value, uint64_t seed) -> HashCode {
   Hasher hasher(seed);
   Hasher hasher(seed);
   hasher.Hash(MapNullPtrToVoidPtr(value));
   hasher.Hash(MapNullPtrToVoidPtr(value));
   return static_cast<HashCode>(hasher);
   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)
 inline auto CarbonHashValue(const std::tuple<Ts...>& value, uint64_t seed)
     -> HashCode {
     -> HashCode {
   Hasher hasher(seed);
   Hasher hasher(seed);
@@ -516,18 +517,17 @@ inline auto CarbonHashValue(const std::tuple<Ts...>& value, uint64_t seed)
   return static_cast<HashCode>(hasher);
   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)
 inline auto CarbonHashValue(const std::pair<T, U>& value, uint64_t seed)
     -> HashCode {
     -> HashCode {
   return CarbonHashValue(std::tuple(value.first, value.second), seed);
   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 {
 inline auto CarbonHashValue(llvm::ArrayRef<T> objs, uint64_t seed) -> HashCode {
   return CarbonHashValue(
   return CarbonHashValue(
       llvm::ArrayRef(reinterpret_cast<const std::byte*>(objs.data()),
       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);
   return CarbonHashValue(value, seed);
 }
 }
 
 
-}  // namespace HashDispatch
+}  // namespace InternalHashDispatch
 
 
 template <typename T>
 template <typename T>
 inline auto HashValue(const T& value, uint64_t seed) -> HashCode {
 inline auto HashValue(const T& value, uint64_t seed) -> HashCode {
-  return HashDispatch::DispatchImpl(value, seed);
+  return InternalHashDispatch::DispatchImpl(value, seed);
 }
 }
 
 
 template <typename T>
 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);
       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 {
 inline auto Hasher::ReadSmall(const T& value) -> uint64_t {
   const auto* storage = reinterpret_cast<const std::byte*>(&value);
   const auto* storage = reinterpret_cast<const std::byte*>(&value);
   if constexpr (sizeof(T) == 1) {
   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 {
 inline auto Hasher::Hash(const T& value) -> void {
   if constexpr (sizeof(T) <= 8) {
   if constexpr (sizeof(T) <= 8) {
     // For types size 8-bytes and smaller directly being hashed (as opposed to
     // 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)));
   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 {
 inline auto Hasher::Hash(const Ts&... value) -> void {
   if constexpr (sizeof...(Ts) == 0) {
   if constexpr (sizeof...(Ts) == 0) {
     buffer ^= StaticRandomData[0];
     buffer ^= StaticRandomData[0];

+ 5 - 5
common/hashing_test.cpp

@@ -7,7 +7,7 @@
 #include <gmock/gmock.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
-#include <type_traits>
+#include <concepts>
 
 
 #include "llvm/ADT/Sequence.h"
 #include "llvm/ADT/Sequence.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/StringExtras.h"
@@ -339,7 +339,8 @@ auto PrintFullWidthHex(llvm::raw_ostream& os, T value) {
                       static_cast<uint64_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)
 auto operator<<(llvm::raw_ostream& os, HashedValue<T> hv)
     -> llvm::raw_ostream& {
     -> llvm::raw_ostream& {
   os << "hash " << hv.hash << " for value ";
   os << "hash " << hv.hash << " for value ";
@@ -347,9 +348,8 @@ auto operator<<(llvm::raw_ostream& os, HashedValue<T> hv)
   return os;
   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)
 auto operator<<(llvm::raw_ostream& os, HashedValue<std::pair<T, U>> hv)
     -> llvm::raw_ostream& {
     -> llvm::raw_ostream& {
   os << "hash " << hv.hash << " for pair of ";
   os << "hash " << hv.hash << " for pair of ";

+ 5 - 6
common/ostream.h

@@ -5,6 +5,7 @@
 #ifndef CARBON_COMMON_OSTREAM_H_
 #ifndef CARBON_COMMON_OSTREAM_H_
 #define CARBON_COMMON_OSTREAM_H_
 #define CARBON_COMMON_OSTREAM_H_
 
 
+#include <concepts>
 #include <ostream>
 #include <ostream>
 #include <type_traits>
 #include <type_traits>
 
 
@@ -72,7 +73,7 @@ namespace llvm {
 //
 //
 // To make this overload be unusually low priority, it is designed to take even
 // 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
 // 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
 // *explicit* operator will be preferred when provided. Some LLVM types may have
 // this, and so we want to prioritize accordingly.
 // 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,
 // `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
 // but supporting `std::ostream` isn't a priority for LLVM so we handle it
 // locally instead.
 // 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& {
 auto operator<<(StreamT& standard_out, const ClassT& value) -> StreamT& {
   raw_os_ostream(standard_out) << value;
   raw_os_ostream(standard_out) << value;
   return standard_out;
   return standard_out;