Prioritize custom printers over default aggregate printing.

This change makes `AbslStringify` and `FuzzTestPrintSourceCode` take precedence over the default field-level printing for aggregate types. If a custom printer is defined for a type, it will be used for both human-readable and source-code modes, with fallback between `AbslStringify` and `FuzzTestPrintSourceCode`. The documentation is updated to reflect this behavior.

This reduces the amount of boilerplate that the user has to write to override the default field-level printing: with the change, it suffices to define `AbslStringify`. This is important for aggregate types that have a nested C-style array, for which the aggregate detection yields a compile-time error.

PiperOrigin-RevId: 869769739
diff --git a/doc/domains-reference.md b/doc/domains-reference.md
index a656179..4db6565 100644
--- a/doc/domains-reference.md
+++ b/doc/domains-reference.md
@@ -53,7 +53,11 @@
 
 User defined structs must support
 [aggregate initialization](https://en.cppreference.com/w/cpp/language/aggregate_initialization),
-must have only public members and no more than 64 fields.
+must have only public members, and no more than 80 fields. In addition, they
+cannot have C-style array members (e.g., `int[5]`).
+
+TIP: If your struct doesn't satisfy the requirements for `Arbitrary`, you can
+construct a domain for it using `Map`, `ReversibleMap`, or `FlatMap`.
 
 Recall that `Arbitrary` is the default input domain, which means that you can
 fuzz a function like below without a `.WithDomains()` clause:
@@ -550,7 +554,7 @@
 This may be useful in cases where 0 is silently interpreted as a sentinel value
 (e.g., "not set").
 
-### Map
+### Map {#map}
 
 Often the best way to define a domain is using a mapping function. The `Map()`
 domain combinator takes a mapping function and an input domain for each of its
@@ -573,6 +577,13 @@
 seeding the input domains passed to `Map()`. Otherwise, if you need full support
 for seeds, consider using [`ReversibleMap()`](#reversible-map).
 
+Note: If your return type is an
+[aggregate type](https://en.cppreference.com/w/cpp/language/aggregate_initialization)
+with a nested C-style array, you may get a compile-time error about a mismatch
+between the number of elements the type binds to and the number of names
+provided (e.g., `binds to 2 elements, but 3 names were provided`). A workaround
+is to define a [custom value printer](#custom-value-printers) for the type.
+
 ### ReversibleMap {#reversible-map}
 
 The `ReversibleMap()` domain combinator is similar to `Map()`: it takes a
@@ -651,6 +662,9 @@
 it `g`) must return `std::nullopt` because there is no possible value it could
 return so that `f(g(std::pair{a, b})) == std::pair{a, b}`.
 
+Note: The [note for `Map`](#map) about aggregate types with nested C-style
+arrays also applies to `ReversibleMap`.
+
 ### FlatMap
 
 Sometimes we need to fuzz parameters that are dependent on each other. Think of
@@ -701,6 +715,9 @@
 [seeded domain](#seeded-domains)—a domain skewed toward certain values—consider
 seeding the input domains passed to `FlatMap()`.
 
+Note: The [note for `Map`](#map) about aggregate types with nested C-style
+arrays also applies to `FlatMap`.
+
 ### Filter
 
 The `Filter` domain combinator takes a domain and a predicate and returns a new
@@ -891,13 +908,14 @@
 constructed using combinators `ConstructorOf`, `Map`, and `FlatMap` don't
 support seeds.
 
-## Customizing Value Printers
+## Customizing Value Printers {#custom-value-printers}
 
 FuzzTest provides a mechanism to display the values that cause a test to fail.
-By default, it knows how to print standard C++ types, but you can extend this
-system to support your own custom types. This is especially useful for making
-test failure reports clear and actionable. There are two ways FuzzTest prints
-values, and you can customize the output for each:
+By default, it knows how to print standard C++ types, including
+[aggregate types](https://en.cppreference.com/w/cpp/language/aggregate_initialization),
+but you can extend this system to support your own custom types. This is
+especially useful for making test failure reports clear and actionable. There
+are two ways FuzzTest prints values, and you can customize the output for each:
 
 -   Human-readable mode: This mode is designed to be easily read and understood
     by a developer. The goal is clarity, not necessarily compilable code.
@@ -907,9 +925,21 @@
 
 ### Customizing the human-readable printer
 
-The simplest and most recommended way to implement custom printing for your
-types is to implement `AbslStringify`. This hooks into Abseil's string
-formatting library, which FuzzTest uses internally. For example:
+For aggregate types, FuzzTest already provides a default printer that performs
+field-level printing, but without field names. For example, if you have:
+
+```c++
+struct MyObject {
+  int id;
+  std::string name;
+};
+```
+
+The default output for `MyObject{1, "Alice"}` will be `MyObject{1, "Alice"}`.
+
+To customize the output, the simplest and most recommended way is to implement
+`AbslStringify`. This hooks into Abseil's string formatting library, which
+FuzzTest uses internally. For example:
 
 ```c++
 struct MyObject {
@@ -918,11 +948,19 @@
 
   template <typename Sink>
   friend void AbslStringify(Sink& sink, const MyObject& obj) {
-    absl::Format(&sink, "MyObject[%d](name=\"%s\")", obj.id, obj.name);
+    absl::Format(&sink, "MyObject{.id = %d, .name = \"%s\"}", obj.id, obj.name);
   }
 };
 ```
 
+Now the output for `MyObject{1, "Alice"}` will be
+`MyObject{.id = 1, .name = "Alice"}`.
+
+WARNING: If your aggregate type contains a nested C-style array, the default
+field-type printing may yield a compile-time error (see the
+[note for `Map`](#map)). In this case, you should define a custom printer as
+a workaround.
+
 ### Customizing the source code printer
 
 To provide a custom source code representation (which is used in regression
@@ -953,9 +991,6 @@
 FuzzTest will use the defined function for both the human-readable and the
 source-code mode. If you define both, FuzzTest will use `AbslStringify` for the
 human-readable mode, and `FuzzTestPrintSourceCode` for the source-code mode.
-Note that this fallback behavior primarily applies to non-aggregate types. For
-aggregate types (like simple structs), FuzzTest prefers field-level printing for
-the mode that doesn't have a custom extension point.
 
 NOTE: FuzzTest does not validate the output. You are responsible for ensuring
 that in the source-code mode, the function prints a valid C++ expression that
diff --git a/fuzztest/internal/type_support.h b/fuzztest/internal/type_support.h
index cc30636..eb55471 100644
--- a/fuzztest/internal/type_support.h
+++ b/fuzztest/internal/type_support.h
@@ -221,6 +221,60 @@
   }
 };
 
+// A wrapper around `RawSink` that can be passed to the user's
+// `FuzzTestPrintSourceCode` function, a FTADLE extension point for custom
+// printers. This is so that the function can look more like `AbslStringify`,
+// which is parameterized by the sink type and passes a pointer to the sink to
+// `absl::Format()`.
+struct RawSinkWrapper {
+  domain_implementor::RawSink& raw_sink;
+
+  friend void AbslFormatFlush(RawSinkWrapper* absl_nonnull sink,
+                              absl::string_view part) {
+    absl::Format(sink->raw_sink, "%s", part);
+  }
+};
+
+template <typename T, typename = void>
+struct HasCustomSourceCodePrinter : std::false_type {};
+
+template <typename T>
+struct HasCustomSourceCodePrinter<
+    T, std::enable_if_t<std::is_void<decltype(FuzzTestPrintSourceCode(
+           std::declval<RawSinkWrapper&>(), std::declval<const T&>()))>::value>>
+    : std::true_type {};
+
+template <typename T>
+inline constexpr bool has_custom_source_code_printer_v =
+    HasCustomSourceCodePrinter<T>::value;
+
+template <typename T>
+inline constexpr bool has_custom_printer_v =
+    has_absl_stringify_v<T> || has_custom_source_code_printer_v<T>;
+
+struct CustomPrinter {
+  template <typename T, typename = std::enable_if_t<has_custom_printer_v<T>>>
+  void PrintUserValue(const T& v, domain_implementor::RawSink out,
+                      domain_implementor::PrintMode mode) {
+    RawSinkWrapper sink{out};
+    if (mode == domain_implementor::PrintMode::kHumanReadable) {
+      // Prefer AbslStringify, fall back on source code printer.
+      if constexpr (has_absl_stringify_v<T>) {
+        absl::Format(out, "%v", v);
+      } else {
+        FuzzTestPrintSourceCode(sink, v);
+      }
+    } else {
+      // Prefer source code printer, fall back on AbslStringify.
+      if constexpr (has_custom_source_code_printer_v<T>) {
+        FuzzTestPrintSourceCode(sink, v);
+      } else {
+        absl::Format(out, "%v", v);
+      }
+    }
+  }
+};
+
 template <typename DomainT, typename... Inner>
 struct AggregatePrinter {
   const DomainT& domain;
@@ -230,12 +284,10 @@
   void PrintCorpusValue(const corpus_type_t<DomainT>& v,
                         domain_implementor::RawSink out,
                         domain_implementor::PrintMode mode) const {
-    if (mode == domain_implementor::PrintMode::kHumanReadable) {
-      // In human-readable mode, prefer formatting with Abseil if possible.
-      if constexpr (has_absl_stringify_v<value_type_t<DomainT>>) {
-        absl::Format(out, "%v", domain.GetValue(v));
-        return;
-      }
+    if constexpr (has_custom_printer_v<value_type_t<DomainT>>) {
+      // Prefer the custom printer if there is one.
+      CustomPrinter{}.PrintUserValue(domain.GetValue(v), out, mode);
+      return;
     }
 
     absl::Format(out, "%s", type_name);
@@ -543,55 +595,10 @@
   }
 };
 
-// A wrapper around `RawSink` that can be passed to the user's
-// `FuzzTestPrintSourceCode` function, a FTADLE extension point for custom
-// printers. This is so that the function can look more like `AbslStringify`,
-// which is parameterized by the sink type and passes a pointer to the sink to
-// `absl::Format()`.
-struct RawSinkWrapper {
-  domain_implementor::RawSink& raw_sink;
-
-  friend void AbslFormatFlush(RawSinkWrapper* absl_nonnull sink,
-                              absl::string_view part) {
-    absl::Format(sink->raw_sink, "%s", part);
-  }
-};
-
-template <typename T, typename = void>
-struct HasCustomSourceCodePrinter : std::false_type {};
-
-template <typename T>
-struct HasCustomSourceCodePrinter<
-    T, std::enable_if_t<std::is_void<decltype(FuzzTestPrintSourceCode(
-           std::declval<RawSinkWrapper&>(), std::declval<const T&>()))>::value>>
-    : std::true_type {};
-
-template <typename T>
-inline constexpr bool has_custom_source_code_printer_v =
-    HasCustomSourceCodePrinter<T>::value;
-
-template <typename T>
-inline constexpr bool has_custom_printer_v =
-    has_absl_stringify_v<T> || has_custom_source_code_printer_v<T>;
-
 struct AutodetectAggregatePrinter {
   template <typename T>
   void PrintUserValue(const T& v, domain_implementor::RawSink out,
                       domain_implementor::PrintMode mode) {
-    if (mode == domain_implementor::PrintMode::kHumanReadable) {
-      // In human-readable mode, prefer formatting with Abseil if possible.
-      if constexpr (has_absl_stringify_v<T>) {
-        absl::Format(out, "%v", v);
-        return;
-      }
-    } else {
-      // In source-code mode, prefer custom source-code printer if possible.
-      if constexpr (has_custom_source_code_printer_v<T>) {
-        RawSinkWrapper sink{out};
-        FuzzTestPrintSourceCode(sink, v);
-        return;
-      }
-    }
     std::tuple bound = DetectBindAggregate(v);
     const auto print_one = [&](auto I) {
       if (I > 0) absl::Format(out, ", ");
@@ -606,40 +613,11 @@
   }
 };
 
-template <typename T, typename = std::enable_if_t<has_custom_printer_v<T>>>
-struct CustomPrinter {
-  void PrintUserValue(const T& v, domain_implementor::RawSink out,
-                      domain_implementor::PrintMode mode) {
-    RawSinkWrapper sink{out};
-    if (mode == domain_implementor::PrintMode::kHumanReadable) {
-      // Prefer AbslStringify, fall back on source code printer.
-      if constexpr (has_absl_stringify_v<T>) {
-        absl::Format(out, "%v", v);
-      } else {
-        FuzzTestPrintSourceCode(sink, v);
-      }
-    } else {
-      // Prefer source code printer, fall back on AbslStringify.
-      if constexpr (has_custom_source_code_printer_v<T>) {
-        FuzzTestPrintSourceCode(sink, v);
-      } else {
-        absl::Format(out, "%v", v);
-      }
-    }
-  }
-};
-
 struct UnknownPrinter {
   template <typename T>
   void PrintUserValue(const T& v, domain_implementor::RawSink out,
                       domain_implementor::PrintMode mode) {
     if (mode == domain_implementor::PrintMode::kHumanReadable) {
-      // Try formatting with Abseil. We can't guarantee a good source code
-      // result, but it should be ok for human readable.
-      if constexpr (has_absl_stringify_v<T>) {
-        absl::Format(out, "%v", v);
-        return;
-      }
       // Some standard types have operator<<.
       if constexpr (std::is_scalar_v<T> || is_std_complex_v<T>) {
         absl::Format(out, "%s", absl::FormatStreamed(v));
@@ -654,8 +632,8 @@
 decltype(auto) AutodetectTypePrinter() {
   // The order of these checks somewhat matters. Most of the concrete types have
   // AbslStringify, so they should come first not to be captured by the custom
-  // printer case. The aggregate case is also more specific than the custom
-  // printer case, so it needs to come before.
+  // printer case. The aggregate case comes after the custom case so that the
+  // user can override the default aggregate printer.
   if constexpr (is_protocol_buffer_enum_v<T>) {
     return ProtobufEnumPrinter<const google::protobuf::EnumDescriptor*>{
         google::protobuf::GetEnumDescriptor<T>()};
@@ -676,10 +654,10 @@
     return DurationPrinter{};
   } else if constexpr (std::is_same_v<T, absl::Time>) {
     return TimePrinter{};
+  } else if constexpr (has_custom_printer_v<T>) {
+    return CustomPrinter{};
   } else if constexpr (is_bindable_aggregate_v<T>) {
     return AutodetectAggregatePrinter{};
-  } else if constexpr (has_custom_printer_v<T>) {
-    return CustomPrinter<T>{};
   } else {
     return UnknownPrinter{};
   }
diff --git a/fuzztest/internal/type_support_test.cc b/fuzztest/internal/type_support_test.cc
index 56b2f07..afaa80b 100644
--- a/fuzztest/internal/type_support_test.cc
+++ b/fuzztest/internal/type_support_test.cc
@@ -206,15 +206,59 @@
   EXPECT_EQ(std::string("\000a\223b\"", 5).size(), 5);
 }
 
-struct UserDefinedWithAbslStringify {
-  std::string foo;
+struct AggregateWithoutCustomPrinter {
+  int i;
+  double d;
+  std::string s;
+};
+
+struct AggregateWithAbslStringify {
+  int i;
+  double d;
+  std::string s;
 
   template <typename Sink>
-  friend void AbslStringify(Sink& sink, const UserDefinedWithAbslStringify& v) {
-    absl::Format(&sink, "{foo=\"%s\"}", v.foo);
+  friend void AbslStringify(Sink& sink, const AggregateWithAbslStringify& v) {
+    absl::Format(&sink, "AbslStringify={%d, %.1f, \"%s\"}", v.i, v.d, v.s);
   }
 };
 
+struct AggregateWithCustomSourceCodePrinter {
+  int i;
+  double d;
+  std::string s;
+
+  template <typename Sink>
+  friend void FuzzTestPrintSourceCode(
+      Sink& sink, const AggregateWithCustomSourceCodePrinter& v) {
+    absl::Format(&sink, "CustomSourceCodePrinter={%d, %.1f, \"%s\"}", v.i, v.d,
+                 v.s);
+  }
+};
+
+struct AggregateWithBoth {
+  int i;
+  double d;
+  std::string s;
+
+  template <typename Sink>
+  friend void AbslStringify(Sink& sink, const AggregateWithBoth& v) {
+    absl::Format(&sink, "AbslStringify={%d, %.1f, \"%s\"}", v.i, v.d, v.s);
+  }
+
+  template <typename Sink>
+  friend void FuzzTestPrintSourceCode(Sink& sink, const AggregateWithBoth& v) {
+    absl::Format(&sink, "CustomSourceCodePrinter={%d, %.1f, \"%s\"}", v.i, v.d,
+                 v.s);
+  }
+};
+
+class NonAggregateWithoutCustomPrinter {
+ private:
+  // Needs a private member so that it isn't considered monostate.
+  [[maybe_unused]] int a_ = 0;
+};
+
 TEST(CompoundTest, Printer) {
   EXPECT_THAT(
       TestPrintValue(std::pair(1, 1.5), Arbitrary<std::pair<int, double>>()),
@@ -222,21 +266,27 @@
   EXPECT_THAT(TestPrintValue(std::tuple(2, -3, -0.0),
                              Arbitrary<std::tuple<int, int, double>>()),
               Each("{2, -3, -0.}"));
-
-  struct UserDefined {
-    int i;
-    double d;
-    std::string s;
-  };
-  EXPECT_THAT(TestPrintValue(
-                  std::tuple{2, -3.5, "Foo"},
-                  StructOf<UserDefined>(Arbitrary<int>(), Arbitrary<double>(),
-                                        Arbitrary<std::string>())),
-              Each("UserDefined{2, -3.5, \"Foo\"}"));
-  EXPECT_THAT(
-      TestPrintValue(std::tuple{"Foo"}, StructOf<UserDefinedWithAbslStringify>(
-                                            Arbitrary<std::string>())),
-      ElementsAre("{foo=\"Foo\"}", "UserDefinedWithAbslStringify{\"Foo\"}"));
+  EXPECT_THAT(TestPrintValue(std::tuple{2, -3.5, "Foo"},
+                             StructOf<AggregateWithoutCustomPrinter>(
+                                 Arbitrary<int>(), Arbitrary<double>(),
+                                 Arbitrary<std::string>())),
+              Each("AggregateWithoutCustomPrinter{2, -3.5, \"Foo\"}"));
+  EXPECT_THAT(TestPrintValue(std::tuple{2, -3.5, "Foo"},
+                             StructOf<AggregateWithAbslStringify>(
+                                 Arbitrary<int>(), Arbitrary<double>(),
+                                 Arbitrary<std::string>())),
+              Each("AbslStringify={2, -3.5, \"Foo\"}"));
+  EXPECT_THAT(TestPrintValue(std::tuple{2, -3.5, "Foo"},
+                             StructOf<AggregateWithCustomSourceCodePrinter>(
+                                 Arbitrary<int>(), Arbitrary<double>(),
+                                 Arbitrary<std::string>())),
+              Each("CustomSourceCodePrinter={2, -3.5, \"Foo\"}"));
+  EXPECT_THAT(TestPrintValue(std::tuple{2, -3.5, "Foo"},
+                             StructOf<AggregateWithBoth>(
+                                 Arbitrary<int>(), Arbitrary<double>(),
+                                 Arbitrary<std::string>())),
+              ElementsAre("AbslStringify={2, -3.5, \"Foo\"}",
+                          "CustomSourceCodePrinter={2, -3.5, \"Foo\"}"));
 }
 
 TEST(ProtobufTest, Printer) {
@@ -488,57 +538,14 @@
   EXPECT_THAT(TestPrintValue(UserDefinedEmpty{}), Each("UserDefinedEmpty{}"));
 }
 
-struct AggregateWithoutCustomPrinter {
-  int i = 1;
-  std::pair<std::string, std::string> nested = {"Foo", "Bar"};
-};
-
-struct AggregateWithAbslStringify {
-  int i = 1;
-  std::pair<std::string, std::string> nested = {"Foo", "Bar"};
-
-  template <typename Sink>
-  friend void AbslStringify(Sink& sink, const AggregateWithAbslStringify& s) {
-    absl::Format(&sink, "value={%d, {%s, %s}}", s.i, s.nested.first,
-                 s.nested.second);
-  }
-};
-
-struct AggregateWithCustomSourceCodePrinter {
-  int i = 1;
-  std::pair<std::string, std::string> nested = {"Foo", "Bar"};
-
-  static AggregateWithCustomSourceCodePrinter Make(int i, std::string fst,
-                                                   std::string snd) {
-    return AggregateWithCustomSourceCodePrinter{
-        i, {std::move(fst), std::move(snd)}};
-  }
-
-  template <typename Sink>
-  friend void FuzzTestPrintSourceCode(
-      Sink& sink, const AggregateWithCustomSourceCodePrinter& s) {
-    absl::Format(
-        &sink, "AggregateWithCustomSourceCodePrinter::Make(%d, \"%s\", \"%s\")",
-        s.i, s.nested.first, s.nested.second);
-  }
-};
-
 TEST(AutodetectAggregateTest, Printer) {
   // MonostateTest handles empty tuple and array.
 
   EXPECT_THAT(TestPrintValue(std::tuple{123}), Each("{123}"));
   EXPECT_THAT(TestPrintValue(std::pair{123, 456}), Each("{123, 456}"));
   EXPECT_THAT(TestPrintValue(std::array{123, 456}), Each("{123, 456}"));
-  EXPECT_THAT(TestPrintValue(AggregateWithoutCustomPrinter{}),
-              Each(R"(AggregateWithoutCustomPrinter{1, {"Foo", "Bar"}})"));
-  EXPECT_THAT(TestPrintValue(AggregateWithAbslStringify{}),
-              ElementsAre("value={1, {Foo, Bar}}",
-                          R"(AggregateWithAbslStringify{1, {"Foo", "Bar"}})"));
-  EXPECT_THAT(
-      TestPrintValue(AggregateWithCustomSourceCodePrinter{}),
-      ElementsAre(
-          R"(AggregateWithCustomSourceCodePrinter{1, {"Foo", "Bar"}})",
-          R"(AggregateWithCustomSourceCodePrinter::Make(1, "Foo", "Bar"))"));
+  EXPECT_THAT(TestPrintValue(AggregateWithoutCustomPrinter{2, -3.5, "Foo"}),
+              Each("AggregateWithoutCustomPrinter{2, -3.5, \"Foo\"}"));
 }
 
 TEST(DurationTest, Printer) {
@@ -575,103 +582,27 @@
                           "absl::UnixEpoch() + absl::Seconds(-1290000)"));
 }
 
-class ClassWithoutCustomPrinter {
- private:
-  // Needs a private member so that it isn't monostate or a bindable aggregate.
-  [[maybe_unused]] int a_ = 0;
-};
-
-static_assert(!is_monostate_v<ClassWithoutCustomPrinter>);
-static_assert(!is_bindable_aggregate_v<ClassWithoutCustomPrinter>);
-
-class ClassWithAbslStringify {
- public:
-  ClassWithAbslStringify(int a, std::string b) : a_{a}, b_{std::move(b)} {}
-
-  template <typename Sink>
-  friend void AbslStringify(Sink& sink, const ClassWithAbslStringify& v) {
-    absl::Format(&sink, "value={%d, \"%s\"}", v.a_, v.b_);
-  }
-
- private:
-  int a_ = 0;
-  std::string b_;
-};
-
-class ClassWithCustomSourceCodePrinter {
- public:
-  static ClassWithCustomSourceCodePrinter Make(int a, std::string b) {
-    return ClassWithCustomSourceCodePrinter{a, std::move(b)};
-  }
-
-  template <typename Sink>
-  friend void FuzzTestPrintSourceCode(
-      Sink& sink, const ClassWithCustomSourceCodePrinter& v) {
-    absl::Format(&sink, "ClassWithCustomSourceCodePrinter::Make(%d, \"%s\")",
-                 v.a_, v.b_);
-  }
-
- private:
-  ClassWithCustomSourceCodePrinter(int a, std::string b)
-      : a_{a}, b_{std::move(b)} {}
-
-  int a_ = 0;
-  std::string b_;
-};
-
-class ClassWithAbslStringifyAndCustomSourceCodePrinter {
- public:
-  static ClassWithAbslStringifyAndCustomSourceCodePrinter Make(int a,
-                                                               std::string b) {
-    return ClassWithAbslStringifyAndCustomSourceCodePrinter{a, std::move(b)};
-  }
-
-  template <typename Sink>
-  friend void AbslStringify(
-      Sink& sink, const ClassWithAbslStringifyAndCustomSourceCodePrinter& v) {
-    absl::Format(&sink, "value={a=%d, b=\"%s\"}", v.a_, v.b_);
-  }
-
-  template <typename Sink>
-  friend void FuzzTestPrintSourceCode(
-      Sink& sink, const ClassWithAbslStringifyAndCustomSourceCodePrinter& v) {
-    absl::Format(
-        &sink,
-        "ClassWithAbslStringifyAndCustomSourceCodePrinter::Make(%d, \"%s\")",
-        v.a_, v.b_);
-  }
-
- private:
-  ClassWithAbslStringifyAndCustomSourceCodePrinter(int a, std::string b)
-      : a_{a}, b_{std::move(b)} {}
-
-  int a_ = 0;
-  std::string b_;
-};
-
 TEST(HasCustomPrinterTest, CorrectlyDetectsCustomPrinters) {
-  static_assert(has_custom_printer_v<ClassWithAbslStringify>);
-  static_assert(has_custom_printer_v<ClassWithCustomSourceCodePrinter>);
-  static_assert(
-      has_custom_printer_v<ClassWithAbslStringifyAndCustomSourceCodePrinter>);
-  static_assert(!has_custom_printer_v<ClassWithoutCustomPrinter>);
+  static_assert(has_custom_printer_v<AggregateWithAbslStringify>);
+  static_assert(has_custom_printer_v<AggregateWithCustomSourceCodePrinter>);
+  static_assert(has_custom_printer_v<AggregateWithBoth>);
+  static_assert(!has_custom_printer_v<NonAggregateWithoutCustomPrinter>);
+  static_assert(!has_custom_printer_v<AggregateWithoutCustomPrinter>);
 }
 
 TEST(CustomPrinterTest, PrintsValuesCorrectly) {
-  EXPECT_THAT(TestPrintValue(ClassWithAbslStringify{1, "foo"}),
-              Each("value={1, \"foo\"}"));
-  EXPECT_THAT(TestPrintValue(ClassWithCustomSourceCodePrinter::Make(1, "foo")),
-              Each("ClassWithCustomSourceCodePrinter::Make(1, \"foo\")"));
+  EXPECT_THAT(TestPrintValue(AggregateWithAbslStringify{2, -3.5, "Foo"}),
+              Each("AbslStringify={2, -3.5, \"Foo\"}"));
   EXPECT_THAT(
-      TestPrintValue(
-          ClassWithAbslStringifyAndCustomSourceCodePrinter::Make(1, "foo")),
-      ElementsAre("value={a=1, b=\"foo\"}",
-                  "ClassWithAbslStringifyAndCustomSourceCodePrinter::Make(1, "
-                  "\"foo\")"));
+      TestPrintValue(AggregateWithCustomSourceCodePrinter{2, -3.5, "Foo"}),
+      Each("CustomSourceCodePrinter={2, -3.5, \"Foo\"}"));
+  EXPECT_THAT(TestPrintValue(AggregateWithBoth{2, -3.5, "Foo"}),
+              ElementsAre("AbslStringify={2, -3.5, \"Foo\"}",
+                          "CustomSourceCodePrinter={2, -3.5, \"Foo\"}"));
 }
 
 TEST(UnprintableTest, PrintsUnprintableValues) {
-  EXPECT_THAT(TestPrintValue(ClassWithoutCustomPrinter{}),
+  EXPECT_THAT(TestPrintValue(NonAggregateWithoutCustomPrinter{}),
               Each("<unprintable value>"));
 }