Procházet zdrojové kódy

Simplify ostream.h SFINAE using a helper syntax (#976)

Co-authored-by: Jon Meow <jperkins@google.com>
Geoff Romer před 4 roky
rodič
revize
ad9c10da16
3 změnil soubory, kde provedl 51 přidání a 15 odebrání
  1. 6 0
      common/BUILD
  2. 34 0
      common/metaprogramming.h
  3. 11 15
      common/ostream.h

+ 6 - 0
common/BUILD

@@ -36,10 +36,16 @@ cc_test(
     ],
 )
 
+cc_library(
+    name = "metaprogramming",
+    hdrs = ["metaprogramming.h"],
+)
+
 cc_library(
     name = "ostream",
     hdrs = ["ostream.h"],
     deps = [
+        ":metaprogramming",
         "@llvm-project//llvm:Support",
     ],
 )

+ 34 - 0
common/metaprogramming.h

@@ -0,0 +1,34 @@
+// 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 COMMON_METAPROGRAMMING_H_
+#define COMMON_METAPROGRAMMING_H_
+
+#include <type_traits>
+
+namespace Carbon {
+
+// C++17-compatible emulation of a C++20 `requires` expression, which queries
+// whether a given expression is well-formed. The syntax is best explained
+// in terms of an example:
+//
+// template <typename T>
+// static constexpr bool IsStreamableToRawOstream =
+//     Requires<const T, llvm::raw_ostream>(
+//         [](auto&& t, auto&& out) -> decltype(out << t) {});
+//
+// The expression enclosed in `decltype` is the expression whose validity the
+// trait queries. The lambda parameters declare the names that are used in
+// that expression, and the types of those names are specified by the
+// corresponding template arguments of the `Requires` call. The lambda
+// parameters should always have type `auto&&`, and the template arguments
+// should not have `&` or `&&` qualifiers.
+template <typename... T, typename F>
+constexpr auto Requires(F /* f */) -> bool {
+  return std::is_invocable_v<F, T...>;
+}
+
+}  // namespace Carbon
+
+#endif  // COMMON_METAPROGRAMMING_H_

+ 11 - 15
common/ostream.h

@@ -7,25 +7,27 @@
 
 #include <ostream>
 
+#include "common/metaprogramming.h"
 #include "llvm/Support/raw_os_ostream.h"
 #include "llvm/Support/raw_ostream.h"
 
 namespace Carbon {
 
+// True if T has a method `void Print(llvm::raw_ostream& out) const`.
+template <typename T>
+static constexpr bool HasPrintMethod = Requires<const T, llvm::raw_ostream>(
+    [](auto&& t, auto&& out) -> decltype(t.Print(out)) {});
+
 // Support raw_ostream << for types which implement:
 //   void Print(llvm::raw_ostream& out) const;
-template <typename T,
-          typename = std::enable_if_t<
-              std::is_member_function_pointer_v<decltype(&T::Print)>>>
+template <typename T, typename = std::enable_if_t<HasPrintMethod<T>>>
 auto operator<<(llvm::raw_ostream& out, const T& obj) -> llvm::raw_ostream& {
   obj.Print(out);
   return out;
 }
 
 // Prevents raw_ostream << for pointers to printable types.
-template <typename T,
-          typename = std::enable_if_t<
-              std::is_member_function_pointer_v<decltype(&T::Print)>>>
+template <typename T, typename = std::enable_if_t<HasPrintMethod<T>>>
 __attribute__((unavailable(
     "Received a pointer to a printable type, are you missing a `*`? "
     "To print as a pointer, cast to void*."))) auto
@@ -33,9 +35,7 @@ operator<<(llvm::raw_ostream& out, const T* /*obj*/) -> llvm::raw_ostream&;
 
 // Support std::ostream << for types which implement:
 //   void Print(llvm::raw_ostream& out) const;
-template <typename T,
-          typename = std::enable_if_t<
-              std::is_member_function_pointer_v<decltype(&T::Print)>>>
+template <typename T, typename = std::enable_if_t<HasPrintMethod<T>>>
 auto operator<<(std::ostream& out, const T& obj) -> std::ostream& {
   llvm::raw_os_ostream raw_os(out);
   obj.Print(raw_os);
@@ -43,9 +43,7 @@ auto operator<<(std::ostream& out, const T& obj) -> std::ostream& {
 }
 
 // Prevents std::ostream << for pointers to printable types.
-template <typename T,
-          typename = std::enable_if_t<
-              std::is_member_function_pointer_v<decltype(&T::Print)>>>
+template <typename T, typename = std::enable_if_t<HasPrintMethod<T>>>
 __attribute__((unavailable(
     "Received a pointer to a printable type, are you missing a `*`? "
     "To print as a pointer, cast to void*."))) auto
@@ -53,9 +51,7 @@ operator<<(std::ostream& out, const T* /*obj*/) -> std::ostream&;
 
 // Allow GoogleTest and GoogleMock to print even pointers by dereferencing them.
 // This is important to allow automatic printing of arguments of mocked APIs.
-template <typename T,
-          typename = std::enable_if_t<
-              std::is_member_function_pointer_v<decltype(&T::Print)>>>
+template <typename T, typename = std::enable_if_t<HasPrintMethod<T>>>
 void PrintTo(T* p, std::ostream* out) {
   *out << static_cast<const void*>(p);