Refactor the code to introduce CorpusType as a type parameter in DomainBase.

This simplifies the code a bit:

1. DomainBase knows about CorpusType and it can use it in its implementation.
   This will also simplify future extensions, e.g., seeded domains.

2. The derived domains no longer explicitly define `value_type`, `corpus_type`,
   and `has_custom_corpus_type`. Instead, they inherit them from DomainBase.
   Moreover, `has_custom_corpus_type` is now simply defined to be true if the
   value type and corpus type are not the same.

PiperOrigin-RevId: 520941062
diff --git a/domain_tests/BUILD b/domain_tests/BUILD
index 02f4527..b7ea683 100644
--- a/domain_tests/BUILD
+++ b/domain_tests/BUILD
@@ -57,8 +57,11 @@
     srcs = ["arbitrary_domains_test.cc"],
     deps = [
         ":domain_testing",
+        "@com_google_absl//absl/container:flat_hash_map",
         "@com_google_absl//absl/container:flat_hash_set",
+        "@com_google_absl//absl/random",
         "@com_google_absl//absl/random:bit_gen_ref",
+        "@com_google_absl//absl/status",
         "@com_google_absl//absl/time",
         "@com_google_fuzztest//fuzztest:absl_helpers",
         "@com_google_fuzztest//fuzztest:domain",
diff --git a/domain_tests/arbitrary_domains_test.cc b/domain_tests/arbitrary_domains_test.cc
index f8f78d7..ab2a97b 100644
--- a/domain_tests/arbitrary_domains_test.cc
+++ b/domain_tests/arbitrary_domains_test.cc
@@ -18,6 +18,7 @@
 #include <cmath>
 #include <cstdint>
 #include <limits>
+#include <list>
 #include <memory>
 #include <optional>
 #include <string>
@@ -31,8 +32,11 @@
 #include "google/protobuf/descriptor.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "absl/container/flat_hash_map.h"
 #include "absl/container/flat_hash_set.h"
 #include "absl/random/bit_gen_ref.h"
+#include "absl/random/random.h"
+#include "absl/status/status.h"
 #include "absl/time/time.h"
 #include "./fuzztest/domain.h"
 #include "./domain_tests/domain_testing.h"
@@ -151,12 +155,10 @@
 }
 
 struct StatefulIncrementDomain
-    : public internal::DomainBase<StatefulIncrementDomain, int> {
-  using value_type = int;
-  // Just to make sure we don't mix value_type with corpus_type
-  using corpus_type = std::tuple<int>;
-  static constexpr bool has_custom_corpus_type = true;
-
+    : public internal::DomainBase<StatefulIncrementDomain, int,
+                                  // Just to make sure we don't mix value_type
+                                  // with corpus_type
+                                  std::tuple<int>> {
   corpus_type Init(absl::BitGenRef prng) {
     // Minimal code to exercise prng.
     corpus_type result = {absl::Uniform<value_type>(prng, i, i + 1)};
diff --git a/domain_tests/container_combinators_test.cc b/domain_tests/container_combinators_test.cc
index 6247c83..664124a 100644
--- a/domain_tests/container_combinators_test.cc
+++ b/domain_tests/container_combinators_test.cc
@@ -79,7 +79,7 @@
     // Basic checks to make sure we have a few sizes and values.
     // TODO: Check these values in a more principled way.
     absl::flat_hash_map<size_t, size_t> size_distribution;
-    absl::flat_hash_map<typename T::value_type, size_t> value_distribution;
+    absl::flat_hash_map<internal::value_type_t<T>, size_t> value_distribution;
     for (const auto& s : values) {
       ++size_distribution[s.user_value.size()];
       for (const auto& v : s.user_value) ++value_distribution[v];
@@ -156,7 +156,7 @@
   TestMinMaxContainerSize(Arbitrary<T>().WithMaxSize(7), 0, 7);
   TestMinMaxContainerSize(Arbitrary<T>().WithMinSize(3).WithMaxSize(7), 3, 7);
 
-  auto inner = Arbitrary<typename T::value_type>();
+  auto inner = Arbitrary<internal::value_type_t<T>>();
 
   TestMinMaxContainerSize(ContainerOf<T>(inner).WithSize(7), 7, 7);
   TestMinMaxContainerSize(ContainerOf<T>(inner).WithMinSize(7), 7, ~size_t{});
@@ -289,7 +289,8 @@
   // Note that we avoid using testing::IsSubsetOf(some_values) here because it
   // isn't optimized for some_values being an associative collection of values.
   using ArgT = std::remove_reference_t<decltype(arg)>;
-  absl::flat_hash_set<typename ArgT::value_type> copy(arg.begin(), arg.end());
+  absl::flat_hash_set<internal::value_type_t<ArgT>> copy(arg.begin(),
+                                                         arg.end());
   return arg.size() == copy.size();
 }
 
diff --git a/domain_tests/domain_testing.h b/domain_tests/domain_testing.h
index ec01ec2..78760d9 100644
--- a/domain_tests/domain_testing.h
+++ b/domain_tests/domain_testing.h
@@ -68,7 +68,7 @@
       return !o || std::isnan(*o) ? 0 : absl::Hash<T>{}(*o);
     } else if constexpr (internal::Requires<T>(
                              [](auto v) -> decltype(v.hash_function()) {})) {
-      return (*this)(std::set<typename T::value_type>(v.begin(), v.end()));
+      return (*this)(std::set<internal::value_type_t<T>>(v.begin(), v.end()));
     } else {
       return absl::Hash<T>{}(v);
     }
@@ -106,7 +106,7 @@
 // simplify their access and mutation.
 template <typename Domain>
 struct Value {
-  using T = typename Domain::value_type;
+  using T = internal::value_type_t<Domain>;
   internal::corpus_type_t<Domain> corpus_value;
   T user_value;
 
@@ -137,8 +137,8 @@
       return H::combine(std::move(state), o);
     } else if constexpr (internal::Requires<T>(
                              [](auto v) -> decltype(v.hash_function()) {})) {
-      return H::combine(std::move(state),
-                        std::set<typename T::value_type>(v.begin(), v.end()));
+      return H::combine(std::move(state), std::set<internal::value_type_t<T>>(
+                                              v.begin(), v.end()));
     } else {
       return H::combine(std::move(state), v);
     }
@@ -266,7 +266,7 @@
 }
 
 template <typename Domain, typename IsTerminal, typename IsCloser,
-          typename T = typename Domain::value_type>
+          typename T = internal::value_type_t<Domain>>
 absl::Status TestShrink(Domain domain,
                         const absl::flat_hash_set<Value<Domain>>& values,
                         IsTerminal is_terminal, IsCloser is_closer_to_zero) {
diff --git a/fuzztest/BUILD b/fuzztest/BUILD
index 6b120e3..b0e484d 100644
--- a/fuzztest/BUILD
+++ b/fuzztest/BUILD
@@ -82,6 +82,7 @@
     name = "table_of_recent_compares",
     hdrs = ["internal/table_of_recent_compares.h"],
     deps = [
+        ":type_support",
         "@com_google_absl//absl/container:flat_hash_set",
         "@com_google_absl//absl/random:bit_gen_ref",
         "@com_google_absl//absl/random:distributions",
diff --git a/fuzztest/domain.h b/fuzztest/domain.h
index 401332b..9a58420 100644
--- a/fuzztest/domain.h
+++ b/fuzztest/domain.h
@@ -79,8 +79,11 @@
   using corpus_type = internal::GenericDomainCorpusType;
   static constexpr bool has_custom_corpus_type = true;
 
-  template <typename Inner>
-  Domain(const internal::DomainBase<Inner, T>& inner)
+  // Intentionally not marked as explicit to allow implicit conversion from the
+  // internal domain implementations.
+  template <int&... ExplicitArgumentBarrier, typename Inner,
+            typename CorpusType>
+  Domain(const internal::DomainBase<Inner, T, CorpusType>& inner)
       : inner_(new auto(static_cast<const Inner&>(inner))) {}
 
   Domain(const Domain& other) { *this = other; }
@@ -251,11 +254,13 @@
   // Domains that uses a layer of indirection. This allows us to create domains
   // for recursive data structures.
   template <typename T>
-  class IndirectDomain : public internal::DomainBase<IndirectDomain<T>> {
+  class IndirectDomain
+      : public internal::DomainBase<IndirectDomain<T>,
+                                    internal::value_type_t<Domain<T>>,
+                                    internal::corpus_type_t<Domain<T>>> {
    public:
-    using value_type = typename Domain<T>::value_type;
-    using corpus_type = typename Domain<T>::corpus_type;
-    static constexpr bool has_custom_corpus_type = true;
+    using typename IndirectDomain::DomainBase::corpus_type;
+    using typename IndirectDomain::DomainBase::value_type;
 
     explicit IndirectDomain(internal::MoveOnlyAny* indirect)
         : indirect_inner_(indirect) {}
@@ -301,16 +306,18 @@
   // Same as Domain<T>, but also holds ownership of the lookup table.
   // This is for toplevel domains.
   template <typename T>
-  class OwningDomain : public internal::DomainBase<OwningDomain<T>> {
+  class OwningDomain
+      : public internal::DomainBase<OwningDomain<T>,
+                                    internal::value_type_t<Domain<T>>,
+                                    internal::corpus_type_t<Domain<T>>> {
    public:
+    using typename OwningDomain::DomainBase::corpus_type;
+    using typename OwningDomain::DomainBase::value_type;
+
     OwningDomain(const Domain<T>& inner,
                  std::unique_ptr<DomainLookUpTable> domain_lookup_table)
         : inner_(inner), domain_lookup_table_(std::move(domain_lookup_table)) {}
 
-    using value_type = typename Domain<T>::value_type;
-    using corpus_type = typename Domain<T>::corpus_type;
-    static constexpr bool has_custom_corpus_type = true;
-
     corpus_type Init(absl::BitGenRef prng) { return inner_.Init(prng); }
 
     void Mutate(corpus_type& val, absl::BitGenRef prng, bool only_shrink) {
@@ -557,8 +564,8 @@
 template <typename T, int&... ExplicitArgumentBarrier, typename Inner>
 auto ContainerOf(Inner inner) {
   static_assert(
-      std::is_same_v<internal::DropConst<typename T::value_type>,
-                     internal::DropConst<typename Inner::value_type>>);
+      std::is_same_v<internal::DropConst<internal::value_type_t<T>>,
+                     internal::DropConst<internal::value_type_t<Inner>>>);
   return internal::ContainerOfImpl<T, Inner>(std::move(inner));
 }
 
@@ -572,11 +579,11 @@
 //
 template <template <typename, typename...> class T,
           int&... ExplicitArgumentBarrier, typename Inner,
-          typename C = T<typename Inner::value_type>>
+          typename C = T<internal::value_type_t<Inner>>>
 auto ContainerOf(Inner inner) {
   static_assert(
-      std::is_same_v<internal::DropConst<typename C::value_type>,
-                     internal::DropConst<typename Inner::value_type>>);
+      std::is_same_v<internal::DropConst<internal::value_type_t<C>>,
+                     internal::DropConst<internal::value_type_t<Inner>>>);
   return internal::ContainerOfImpl<C, Inner>(std::move(inner));
 }
 
@@ -654,7 +661,7 @@
 template <int&... ExplicitArgumentBarrier, typename Inner1, typename Inner2>
 auto PairOf(Inner1 inner1, Inner2 inner2) {
   return internal::AggregateOfImpl<
-      std::pair<typename Inner1::value_type, typename Inner2::value_type>,
+      std::pair<internal::value_type_t<Inner1>, internal::value_type_t<Inner2>>,
       internal::RequireCustomCorpusType::kNo, Inner1, Inner2>(
       std::in_place, std::move(inner1), std::move(inner2));
 }
@@ -668,7 +675,7 @@
 //
 template <int&... ExplicitArgumentBarrier, typename... Inner>
 auto TupleOf(Inner... inner) {
-  return internal::AggregateOfImpl<std::tuple<typename Inner::value_type...>,
+  return internal::AggregateOfImpl<std::tuple<internal::value_type_t<Inner>...>,
                                    internal::RequireCustomCorpusType::kNo,
                                    Inner...>(std::in_place,
                                              std::move(inner)...);
@@ -698,7 +705,7 @@
 
 template <int&... ExplicitArgumentBarrier, typename... Inner>
 auto VariantOf(Inner... inner) {
-  return VariantOf<std::variant<typename Inner::value_type...>>(
+  return VariantOf<std::variant<internal::value_type_t<Inner>...>>(
       std::move(inner)...);
 }
 
@@ -725,7 +732,7 @@
 
 template <int&... ExplicitArgumentBarrier, typename Inner>
 auto OptionalOf(Inner inner) {
-  return OptionalOf<std::optional<typename Inner::value_type>>(
+  return OptionalOf<std::optional<internal::value_type_t<Inner>>>(
       std::move(inner));
 }
 
@@ -775,7 +782,7 @@
 //
 template <int&... ExplicitArgumentBarrier, typename Inner>
 auto UniquePtrOf(Inner inner) {
-  return SmartPointerOf<std::unique_ptr<typename Inner::value_type>>(
+  return SmartPointerOf<std::unique_ptr<internal::value_type_t<Inner>>>(
       std::move(inner));
 }
 
@@ -788,7 +795,7 @@
 //
 template <int&... ExplicitArgumentBarrier, typename Inner>
 auto SharedPtrOf(Inner inner) {
-  return SmartPointerOf<std::shared_ptr<typename Inner::value_type>>(
+  return SmartPointerOf<std::shared_ptr<internal::value_type_t<Inner>>>(
       std::move(inner));
 }
 
@@ -834,7 +841,8 @@
 //
 template <int&... ExplicitArgumentBarrier, typename Inner>
 auto VectorOf(Inner inner) {
-  return ContainerOf<std::vector<typename Inner::value_type>>(std::move(inner));
+  return ContainerOf<std::vector<internal::value_type_t<Inner>>>(
+      std::move(inner));
 }
 
 // DequeOf(inner) combinator creates a `std::deque` domain with elements of the
@@ -846,7 +854,8 @@
 //
 template <int&... ExplicitArgumentBarrier, typename Inner>
 auto DequeOf(Inner inner) {
-  return ContainerOf<std::deque<typename Inner::value_type>>(std::move(inner));
+  return ContainerOf<std::deque<internal::value_type_t<Inner>>>(
+      std::move(inner));
 }
 
 // ListOf(inner) combinator creates a `std::list` domain with elements of the
@@ -858,7 +867,8 @@
 //
 template <int&... ExplicitArgumentBarrier, typename Inner>
 auto ListOf(Inner inner) {
-  return ContainerOf<std::list<typename Inner::value_type>>(std::move(inner));
+  return ContainerOf<std::list<internal::value_type_t<Inner>>>(
+      std::move(inner));
 }
 
 // SetOf(inner) combinator creates a `std::set` domain with elements of the
@@ -870,7 +880,7 @@
 //
 template <int&... ExplicitArgumentBarrier, typename Inner>
 auto SetOf(Inner inner) {
-  return ContainerOf<std::set<typename Inner::value_type>>(std::move(inner));
+  return ContainerOf<std::set<internal::value_type_t<Inner>>>(std::move(inner));
 }
 
 // MapOf(key_domain, value_domain) combinator creates a `std::map` domain with
@@ -883,8 +893,8 @@
 template <int&... ExplicitArgumentBarrier, typename KeyDomain,
           typename ValueDomain>
 auto MapOf(KeyDomain key_domain, ValueDomain value_domain) {
-  return ContainerOf<std::map<typename KeyDomain::value_type,
-                              typename ValueDomain::value_type>>(
+  return ContainerOf<std::map<internal::value_type_t<KeyDomain>,
+                              internal::value_type_t<ValueDomain>>>(
       PairOf(std::move(key_domain), std::move(value_domain)));
 }
 
@@ -897,7 +907,7 @@
 //
 template <int&... ExplicitArgumentBarrier, typename Inner>
 auto UnorderedSetOf(Inner inner) {
-  return ContainerOf<std::unordered_set<typename Inner::value_type>>(
+  return ContainerOf<std::unordered_set<internal::value_type_t<Inner>>>(
       std::move(inner));
 }
 
@@ -912,8 +922,8 @@
 template <int&... ExplicitArgumentBarrier, typename KeyDomain,
           typename ValueDomain>
 auto UnorderedMapOf(KeyDomain key_domain, ValueDomain value_domain) {
-  return ContainerOf<std::unordered_map<typename KeyDomain::value_type,
-                                        typename ValueDomain::value_type>>(
+  return ContainerOf<std::unordered_map<internal::value_type_t<KeyDomain>,
+                                        internal::value_type_t<ValueDomain>>>(
       PairOf(std::move(key_domain), std::move(value_domain)));
 }
 
@@ -941,9 +951,9 @@
   // All value_types of inner domains must be the same, though they can have
   // different corpus_types.
   using value_type =
-      typename std::tuple_element_t<0, std::tuple<Inner...>>::value_type;
+      internal::value_type_t<std::tuple_element_t<0, std::tuple<Inner...>>>;
   static_assert(std::conjunction_v<
-                    std::is_same<value_type, typename Inner::value_type>...>,
+                    std::is_same<value_type, internal::value_type_t<Inner>>...>,
                 "All domains in a ArrayOf must have the same value_type.");
   return internal::AggregateOfImpl<std::array<value_type, sizeof...(Inner)>,
                                    internal::RequireCustomCorpusType::kNo,
@@ -990,8 +1000,8 @@
 template <typename T, int&... ExplicitArgumentBarrier, typename Inner>
 auto UniqueElementsContainerOf(Inner inner) {
   static_assert(
-      std::is_same_v<internal::DropConst<typename T::value_type>,
-                     internal::DropConst<typename Inner::value_type>>);
+      std::is_same_v<internal::DropConst<internal::value_type_t<T>>,
+                     internal::DropConst<internal::value_type_t<Inner>>>);
   return internal::UniqueElementsContainerImpl<T, Inner>(std::move(inner));
 }
 
@@ -1007,7 +1017,7 @@
 //
 template <typename Inner>
 auto UniqueElementsVectorOf(Inner inner) {
-  return UniqueElementsContainerOf<std::vector<typename Inner::value_type>>(
+  return UniqueElementsContainerOf<std::vector<internal::value_type_t<Inner>>>(
       std::move(inner));
 }
 
diff --git a/fuzztest/internal/domains/aggregate_of_impl.h b/fuzztest/internal/domains/aggregate_of_impl.h
index e93932d..674ad39 100644
--- a/fuzztest/internal/domains/aggregate_of_impl.h
+++ b/fuzztest/internal/domains/aggregate_of_impl.h
@@ -35,19 +35,23 @@
 
 enum class RequireCustomCorpusType { kNo, kYes };
 
+// For user defined types (structs) we require a custom corpus_type
+// (std::tuple), because the serializer does not support structs, only tuples.
+template <typename T, RequireCustomCorpusType require_custom, typename... Inner>
+using AggregateOfImplCorpusType =
+    std::conditional_t<require_custom == RequireCustomCorpusType::kYes ||
+                           (Inner::has_custom_corpus_type || ...),
+                       std::tuple<corpus_type_t<Inner>...>, T>;
+
 template <typename T, RequireCustomCorpusType require_custom, typename... Inner>
 class AggregateOfImpl
-    : public DomainBase<AggregateOfImpl<T, require_custom, Inner...>, T> {
+    : public DomainBase<
+          AggregateOfImpl<T, require_custom, Inner...>, T,
+          AggregateOfImplCorpusType<T, require_custom, Inner...>> {
  public:
-  using value_type = T;
-  // For user defined types (structs) we require a custom corpus_type
-  // (std::tuple), because the serializer does not support structs, only tuples.
-  static constexpr bool has_custom_corpus_type =
-      require_custom == RequireCustomCorpusType::kYes ||
-      (Inner::has_custom_corpus_type || ...);
-  using corpus_type =
-      std::conditional_t<has_custom_corpus_type,
-                         std::tuple<corpus_type_t<Inner>...>, T>;
+  using AggregateOfImpl::DomainBase::has_custom_corpus_type;
+  using typename AggregateOfImpl::DomainBase::corpus_type;
+  using typename AggregateOfImpl::DomainBase::value_type;
 
   AggregateOfImpl() = default;
   explicit AggregateOfImpl(std::in_place_t, Inner... inner)
@@ -106,8 +110,7 @@
         if (*tuple_elem >= 0 && *tuple_elem < sizeof...(Inner)) {
           Switch<sizeof...(Inner)>(*tuple_elem, [&](auto I) {
             PrintValue(std::get<I>(inner_),
-                       std::get<I>(val.GetAs<corpus_type_t<AggregateOfImpl>>()),
-                       out, mode);
+                       std::get<I>(val.GetAs<corpus_type>()), out, mode);
           });
         }
       }
diff --git a/fuzztest/internal/domains/arbitrary_impl.h b/fuzztest/internal/domains/arbitrary_impl.h
index a641885..e10187c 100644
--- a/fuzztest/internal/domains/arbitrary_impl.h
+++ b/fuzztest/internal/domains/arbitrary_impl.h
@@ -67,7 +67,7 @@
 class ArbitraryImpl<T, std::enable_if_t<is_monostate_v<T>>>
     : public DomainBase<ArbitraryImpl<T>> {
  public:
-  using value_type = T;
+  using typename ArbitraryImpl::DomainBase::value_type;
 
   value_type Init(absl::BitGenRef) { return value_type{}; }
 
@@ -80,8 +80,6 @@
 template <>
 class ArbitraryImpl<bool> : public DomainBase<ArbitraryImpl<bool>> {
  public:
-  using value_type = bool;
-
   value_type Init(absl::BitGenRef prng) {
     return static_cast<bool>(absl::Uniform(prng, 0, 2));
   }
@@ -103,7 +101,8 @@
                                         std::numeric_limits<T>::is_integer>>
     : public DomainBase<ArbitraryImpl<T>> {
  public:
-  using value_type = T;
+  using typename ArbitraryImpl::DomainBase::value_type;
+
   static constexpr bool is_memory_dictionary_compatible_v =
       sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8;
   using IntegerDictionaryT =
@@ -191,7 +190,7 @@
 class ArbitraryImpl<T, std::enable_if_t<std::is_floating_point_v<T>>>
     : public DomainBase<ArbitraryImpl<T>> {
  public:
-  using value_type = T;
+  using typename ArbitraryImpl::DomainBase::value_type;
 
   value_type Init(absl::BitGenRef prng) {
     const T special[] = {
@@ -234,20 +233,20 @@
 // Arbitrary for containers.
 template <typename T>
 class ArbitraryImpl<
-    T, std::enable_if_t<always_true<T>,
-                        decltype(
-                            // Iterable
-                            T().begin(), T().end(), T().size(),
-                            // Values are mutable
-                            // This rejects associative containers, for example
-                            // *T().begin() = std::declval<typename
-                            // T::value_type>(), Can insert and erase elements
-                            T().insert(T().end(),
-                                       std::declval<typename T::value_type>()),
-                            T().erase(T().begin()),
-                            //
-                            (void)0)>>
-    : public ContainerOfImpl<T, ArbitraryImpl<typename T::value_type>> {};
+    T,
+    std::enable_if_t<always_true<T>,
+                     decltype(
+                         // Iterable
+                         T().begin(), T().end(), T().size(),
+                         // Values are mutable
+                         // This rejects associative containers, for example
+                         // *T().begin() = std::declval<value_type_t<T>>(),
+                         // Can insert and erase elements
+                         T().insert(T().end(), std::declval<value_type_t<T>>()),
+                         T().erase(T().begin()),
+                         //
+                         (void)0)>>
+    : public ContainerOfImpl<T, ArbitraryImpl<value_type_t<T>>> {};
 
 // Arbitrary for std::string_view.
 //
@@ -255,13 +254,14 @@
 // better. See below.
 template <typename Char>
 class ArbitraryImpl<std::basic_string_view<Char>>
-    : public DomainBase<ArbitraryImpl<std::basic_string_view<Char>>> {
+    : public DomainBase<ArbitraryImpl<std::basic_string_view<Char>>,
+                        std::basic_string_view<Char>,
+                        // We use a vector to better manage the buffer and help
+                        // ASan find out-of-bounds bugs.
+                        std::vector<Char>> {
  public:
-  using value_type = std::string_view;
-  // We use a vector to better manage the buffer and help ASan find
-  // out-of-bounds bugs.
-  using corpus_type = std::vector<Char>;
-  static constexpr bool has_custom_corpus_type = true;
+  using typename ArbitraryImpl::DomainBase::corpus_type;
+  using typename ArbitraryImpl::DomainBase::value_type;
 
   corpus_type Init(absl::BitGenRef prng) { return inner_.Init(prng); }
 
diff --git a/fuzztest/internal/domains/bit_flag_combination_of_impl.h b/fuzztest/internal/domains/bit_flag_combination_of_impl.h
index b612a98..f7ec199 100644
--- a/fuzztest/internal/domains/bit_flag_combination_of_impl.h
+++ b/fuzztest/internal/domains/bit_flag_combination_of_impl.h
@@ -30,7 +30,7 @@
 class BitFlagCombinationOfImpl
     : public DomainBase<BitFlagCombinationOfImpl<T>> {
  public:
-  using value_type = T;
+  using typename BitFlagCombinationOfImpl::DomainBase::value_type;
 
   explicit BitFlagCombinationOfImpl(absl::Span<const T> flags)
       : flags_(flags.begin(), flags.end()) {
diff --git a/fuzztest/internal/domains/container_of_impl.h b/fuzztest/internal/domains/container_of_impl.h
index 219f216..f09e965 100644
--- a/fuzztest/internal/domains/container_of_impl.h
+++ b/fuzztest/internal/domains/container_of_impl.h
@@ -52,23 +52,32 @@
   return std::next(val.begin(), i);
 }
 
+template <typename ContainerDomain,
+          typename ValueType = ExtractTemplateParameter<0, ContainerDomain>,
+          typename InnerDomain = ExtractTemplateParameter<1, ContainerDomain>>
+using ContainerOfImplBaseCorpusType = std::conditional_t<
+    // Specialized handling of vector<bool> since you can't actually hold
+    // a reference to a single bit but instead get a proxy value.
+    is_bitvector_v<ValueType> ||
+        // If the container is associative we force a custom corpus type to
+        // allow modifying the keys.
+        is_associative_container_v<ValueType> ||
+        InnerDomain::has_custom_corpus_type,
+    // Corpus type might be immutable (eg std::pair<const int, int> for maps
+    // inner domain). We store them in a std::list to allow for this.
+    std::list<corpus_type_t<InnerDomain>>, ValueType>;
+
 // Common base for container domains. Provides common APIs.
 template <typename Derived>
-class ContainerOfImplBase : public DomainBase<Derived> {
+class ContainerOfImplBase
+    : public DomainBase<Derived, ExtractTemplateParameter<0, Derived>,
+                        ContainerOfImplBaseCorpusType<Derived>> {
   using InnerDomainT = ExtractTemplateParameter<1, Derived>;
 
  public:
-  using value_type = ExtractTemplateParameter<0, Derived>;
-  static constexpr bool has_custom_corpus_type =
-      // Specialized handling of vector<bool> since you can't actually hold
-      // a reference to a single bit but instead get a proxy value.
-      is_bitvector_v<value_type> ||
-      // If the container is associative we force a custom corpus type to allow
-      // modifying the keys.
-      is_associative_container_v<value_type> ||
-      InnerDomainT::has_custom_corpus_type;
-  // `corpus_type` might be immutable (eg std::pair<const int, int> for maps
-  // inner domain). We store them in a std::list to allow for this.
+  using ContainerOfImplBase::DomainBase::has_custom_corpus_type;
+  using typename ContainerOfImplBase::DomainBase::corpus_type;
+  using typename ContainerOfImplBase::DomainBase::value_type;
 
   // Some container mutation only applies to vector or string types which do
   // not have a custom corpus type.
@@ -83,10 +92,6 @@
       is_memory_dictionary_compatible<InnerDomainT>::value &&
       is_vector_or_string;
 
-  using corpus_type =
-      std::conditional_t<has_custom_corpus_type,
-                         std::list<corpus_type_t<InnerDomainT>>, value_type>;
-
   // If `!container_has_memory_dict`, dict_type is a bool and dict
   // is not used. This conditional_t may be neccessary because some
   // value_type may not have copy constructors(for example, proto).
@@ -281,7 +286,7 @@
 
   InnerDomainT Inner() const { return inner_; }
 
-  template <typename OtherDerived>
+  template <typename>
   friend class ContainerOfImplBase;
 
   template <typename OtherDerived>
@@ -367,10 +372,9 @@
   using Base = typename AssociativeContainerOfImpl::ContainerOfImplBase;
 
  public:
-  using value_type = T;
-  using corpus_type = typename Base::corpus_type;
-  static constexpr bool has_custom_corpus_type = Base::has_custom_corpus_type;
-  static_assert(has_custom_corpus_type, "Must be custom to mutate keys");
+  using typename Base::corpus_type;
+
+  static_assert(Base::has_custom_corpus_type, "Must be custom to mutate keys");
 
   AssociativeContainerOfImpl() = default;
   explicit AssociativeContainerOfImpl(InnerDomain inner)
@@ -475,8 +479,7 @@
   using Base = typename SequenceContainerOfImpl::ContainerOfImplBase;
 
  public:
-  using value_type = T;
-  using corpus_type = typename Base::corpus_type;
+  using typename Base::corpus_type;
 
   SequenceContainerOfImpl() = default;
   explicit SequenceContainerOfImpl(InnerDomain inner)
diff --git a/fuzztest/internal/domains/domain_base.h b/fuzztest/internal/domains/domain_base.h
index 837579d..e8bdb2d 100644
--- a/fuzztest/internal/domains/domain_base.h
+++ b/fuzztest/internal/domains/domain_base.h
@@ -39,7 +39,7 @@
 // of just "false".
 template <typename T, typename U>
 constexpr void CheckIsSame() {
-  static_assert(std::is_same_v<std::remove_const_t<T>, std::remove_const_t<U>>);
+  static_assert(std::is_same_v<T, U>);
 }
 
 // Corpus value type used by Domain<T> template, regardless of T.
@@ -99,9 +99,15 @@
 };
 
 template <typename Derived,
-          typename ValueType = ExtractTemplateParameter<0, Derived>>
+          typename ValueType = ExtractTemplateParameter<0, Derived>,
+          typename CorpusType = ValueType>
 class DomainBase : public TypedDomainInterface<ValueType> {
  public:
+  using value_type = ValueType;
+  using corpus_type = CorpusType;
+  static constexpr bool has_custom_corpus_type =
+      !std::is_same_v<ValueType, CorpusType>;
+
   DomainBase() {
     // Check that the interface of `Derived` matches the requirements for a
     // domain implementation. We check these inside the constructor of
@@ -109,47 +115,37 @@
     // check them at class scope we would see an incomplete `Derived` class and
     // the checks would not work.
 
-    // Has value_type.
-    using CheckValueType = typename Derived::value_type;
-    static_assert(std::is_same_v<ValueType, CheckValueType>);
-    if constexpr (Derived::has_custom_corpus_type) {
-      // The type of values that are mutated and stored internally in the
-      // "corpus" may be different from the type of values produced by the
-      // domain. For example, the corpus_type of InRegexp is a custom data
-      // structure representing a path through a state machine, but the domain
-      // produces values of type std::string.
-      using CheckCorpusType [[maybe_unused]] = typename Derived::corpus_type;
-    } else {
-      CheckIsSame<typename Derived::value_type, corpus_type_t<Derived>>();
-    }
+    CheckIsSame<ValueType, value_type_t<Derived>>();
+    CheckIsSame<CorpusType, corpus_type_t<Derived>>();
+    static_assert(has_custom_corpus_type == Derived::has_custom_corpus_type);
   }
 
   std::unique_ptr<UntypedDomainInterface> Clone() const final {
     return std::make_unique<Derived>(derived());
   }
 
-  GenericDomainCorpusType UntypedInit(absl::BitGenRef ref) final {
-    return GenericDomainCorpusType(std::in_place_type<corpus_type_t<Derived>>,
-                                   derived().Init(ref));
+  GenericDomainCorpusType UntypedInit(absl::BitGenRef prng) final {
+    return GenericDomainCorpusType(std::in_place_type<CorpusType>,
+                                   derived().Init(prng));
   }
 
   void UntypedMutate(GenericDomainCorpusType& val, absl::BitGenRef prng,
                      bool only_shrink) final {
-    derived().Mutate(val.GetAs<corpus_type_t<Derived>>(), prng, only_shrink);
+    derived().Mutate(val.GetAs<CorpusType>(), prng, only_shrink);
   }
 
   void UntypedUpdateMemoryDictionary(const GenericDomainCorpusType& val) final {
-    derived().UpdateMemoryDictionary(val.GetAs<corpus_type_t<Derived>>());
+    derived().UpdateMemoryDictionary(val.GetAs<CorpusType>());
   }
 
   ValueType TypedGetValue(const GenericDomainCorpusType& v) const final {
-    return derived().GetValue(v.GetAs<corpus_type_t<Derived>>());
+    return derived().GetValue(v.GetAs<CorpusType>());
   }
 
   std::optional<GenericDomainCorpusType> TypedFromValue(
       const ValueType& v) const final {
     if (auto c = derived().FromValue(v)) {
-      return GenericDomainCorpusType(std::in_place_type<corpus_type_t<Derived>>,
+      return GenericDomainCorpusType(std::in_place_type<CorpusType>,
                                      *std::move(c));
     } else {
       return std::nullopt;
@@ -159,7 +155,7 @@
   std::optional<GenericDomainCorpusType> UntypedParseCorpus(
       const IRObject& obj) const final {
     if (auto res = derived().ParseCorpus(obj)) {
-      return GenericDomainCorpusType(std::in_place_type<corpus_type_t<Derived>>,
+      return GenericDomainCorpusType(std::in_place_type<CorpusType>,
                                      *std::move(res));
     } else {
       return std::nullopt;
@@ -168,20 +164,18 @@
 
   IRObject UntypedSerializeCorpus(
       const GenericDomainCorpusType& v) const final {
-    return derived().SerializeCorpus(
-        v.template GetAs<corpus_type_t<Derived>>());
+    return derived().SerializeCorpus(v.template GetAs<CorpusType>());
   }
 
   uint64_t UntypedCountNumberOfFields(const GenericDomainCorpusType& v) final {
-    return derived().CountNumberOfFields(v.GetAs<corpus_type_t<Derived>>());
+    return derived().CountNumberOfFields(v.GetAs<CorpusType>());
   }
 
   uint64_t UntypedMutateSelectedField(GenericDomainCorpusType& v,
                                       absl::BitGenRef prng, bool only_shrink,
                                       uint64_t selected_field_index) final {
-    return derived().MutateSelectedField(v.GetAs<corpus_type_t<Derived>>(),
-                                         prng, only_shrink,
-                                         selected_field_index);
+    return derived().MutateSelectedField(v.GetAs<CorpusType>(), prng,
+                                         only_shrink, selected_field_index);
   }
 
   int UntypedPrintCorpusValue(const GenericDomainCorpusType& val,
@@ -190,50 +184,39 @@
     FUZZTEST_INTERNAL_CHECK(
         !tuple_elem.has_value(),
         "No tuple element should be specified for this override.");
-    internal::PrintValue(derived(), val.GetAs<corpus_type_t<Derived>>(), out,
-                         mode);
+    internal::PrintValue(derived(), val.GetAs<CorpusType>(), out, mode);
     return -1;
   }
 
   // Default GetValue and FromValue functions for !has_custom_corpus_type
   // domains.
   ValueType GetValue(const ValueType& v) const {
-    static_assert(!Derived::has_custom_corpus_type);
+    static_assert(!has_custom_corpus_type);
     return v;
   }
   std::optional<ValueType> FromValue(const ValueType& v) const {
-    static_assert(!Derived::has_custom_corpus_type);
+    static_assert(!has_custom_corpus_type);
     return v;
   }
 
-  template <typename D = Derived>
-  std::optional<corpus_type_t<D>> ParseCorpus(const IRObject& obj) const {
-    static_assert(!D::has_custom_corpus_type);
-    return obj.ToCorpus<corpus_type_t<D>>();
+  std::optional<CorpusType> ParseCorpus(const IRObject& obj) const {
+    static_assert(!has_custom_corpus_type);
+    return obj.ToCorpus<CorpusType>();
   }
 
-  template <typename D = Derived>
-  IRObject SerializeCorpus(const corpus_type_t<D>& v) const {
-    static_assert(!D::has_custom_corpus_type);
+  IRObject SerializeCorpus(const CorpusType& v) const {
+    static_assert(!has_custom_corpus_type);
     return IRObject::FromCorpus(v);
   }
 
-  template <typename D = Derived>
-  void UpdateMemoryDictionary(const corpus_type_t<D>& val) {}
+  void UpdateMemoryDictionary(const CorpusType& val) {}
 
-  template <typename D = Derived>
-  uint64_t CountNumberOfFields(const corpus_type_t<D>&) {
+  uint64_t CountNumberOfFields(const CorpusType&) { return 0; }
+
+  uint64_t MutateSelectedField(CorpusType&, absl::BitGenRef, bool, uint64_t) {
     return 0;
   }
 
-  template <typename D = Derived>
-  uint64_t MutateSelectedField(corpus_type_t<D>&, absl::BitGenRef, bool,
-                               uint64_t) {
-    return 0;
-  }
-
-  static constexpr bool has_custom_corpus_type = false;
-
  private:
   Derived& derived() { return static_cast<Derived&>(*this); }
   const Derived& derived() const { return static_cast<const Derived&>(*this); }
diff --git a/fuzztest/internal/domains/element_of_impl.h b/fuzztest/internal/domains/element_of_impl.h
index 39a4d1d..bf53844 100644
--- a/fuzztest/internal/domains/element_of_impl.h
+++ b/fuzztest/internal/domains/element_of_impl.h
@@ -30,12 +30,14 @@
 
 namespace fuzztest::internal {
 
+enum class ElementOfImplCorpusType : size_t;
+
 template <typename T>
-class ElementOfImpl : public DomainBase<ElementOfImpl<T>> {
+class ElementOfImpl
+    : public DomainBase<ElementOfImpl<T>, T, ElementOfImplCorpusType> {
  public:
-  using value_type = T;
-  enum class corpus_type : size_t;
-  static constexpr bool has_custom_corpus_type = true;
+  using typename ElementOfImpl::DomainBase::corpus_type;
+  using typename ElementOfImpl::DomainBase::value_type;
 
   explicit ElementOfImpl(std::vector<T> values) : values_(values) {
     FUZZTEST_INTERNAL_CHECK_PRECONDITION(
diff --git a/fuzztest/internal/domains/filter_impl.h b/fuzztest/internal/domains/filter_impl.h
index 3061b81..ee13a1a 100644
--- a/fuzztest/internal/domains/filter_impl.h
+++ b/fuzztest/internal/domains/filter_impl.h
@@ -29,11 +29,11 @@
 
 template <typename Pred, typename Inner>
 class FilterImpl
-    : public DomainBase<FilterImpl<Pred, Inner>, typename Inner::value_type> {
+    : public DomainBase<FilterImpl<Pred, Inner>, value_type_t<Inner>,
+                        corpus_type_t<Inner>> {
  public:
-  using corpus_type = corpus_type_t<Inner>;
-  using value_type = typename Inner::value_type;
-  static constexpr bool has_custom_corpus_type = Inner::has_custom_corpus_type;
+  using typename FilterImpl::DomainBase::corpus_type;
+  using typename FilterImpl::DomainBase::value_type;
 
   FilterImpl() = default;
   explicit FilterImpl(Pred predicate, Inner inner)
diff --git a/fuzztest/internal/domains/flat_map_impl.h b/fuzztest/internal/domains/flat_map_impl.h
index 9fee4e3..0ebdc6c 100644
--- a/fuzztest/internal/domains/flat_map_impl.h
+++ b/fuzztest/internal/domains/flat_map_impl.h
@@ -31,20 +31,23 @@
 namespace fuzztest::internal {
 
 template <typename FlatMapper, typename... Inner>
+using FlatMapOutputDomain = std::decay_t<
+    std::invoke_result_t<FlatMapper, const value_type_t<Inner>&...>>;
+
+template <typename FlatMapper, typename... Inner>
 class FlatMapImpl
     : public DomainBase<
           FlatMapImpl<FlatMapper, Inner...>,
-          typename std::decay_t<std::invoke_result_t<
-              FlatMapper, const typename Inner::value_type&...>>::value_type> {
+          value_type_t<FlatMapOutputDomain<FlatMapper, Inner...>>,
+          std::tuple<corpus_type_t<FlatMapOutputDomain<FlatMapper, Inner...>>,
+                     corpus_type_t<Inner>...>> {
  private:
   using output_domain = std::decay_t<
-      std::invoke_result_t<FlatMapper, const typename Inner::value_type&...>>;
+      std::invoke_result_t<FlatMapper, const value_type_t<Inner>&...>>;
 
  public:
-  using corpus_type =
-      std::tuple<corpus_type_t<output_domain>, corpus_type_t<Inner>...>;
-  using value_type = typename output_domain::value_type;
-  static constexpr bool has_custom_corpus_type = true;
+  using typename FlatMapImpl::DomainBase::corpus_type;
+  using typename FlatMapImpl::DomainBase::value_type;
 
   FlatMapImpl() = default;
   explicit FlatMapImpl(FlatMapper mapper, Inner... inner)
diff --git a/fuzztest/internal/domains/in_grammar_impl.h b/fuzztest/internal/domains/in_grammar_impl.h
index d2662fa..7e1e4a9 100644
--- a/fuzztest/internal/domains/in_grammar_impl.h
+++ b/fuzztest/internal/domains/in_grammar_impl.h
@@ -49,9 +49,9 @@
 using ASTTypeId = int;
 struct ASTNode {
   ASTTypeId type_id;
-  std::variant<std::monostate,  // If the node is a string terminal.
-               internal::InRegexpImpl::DFAPath,  // If the node is a regex
-                                                 // terminal.
+  std::variant<std::monostate,        // If the node is a string terminal.
+               DFAPath,               // If the node is a regex
+                                      // terminal.
                std::vector<ASTNode>>  // If the node is a non-terminal.
       children;
 
@@ -163,17 +163,14 @@
 
   static IRObject SerializeCorpus(const ASTNode& astnode) {
     FUZZTEST_INTERNAL_CHECK(
-        CheckASTNodeTypeIdAndChildType<internal::InRegexpImpl::DFAPath>(astnode,
-                                                                        id),
-        "Invalid node!");
-    return WrapASTIntoIRObject(
-        astnode,
-        GetInnerRegexpDomain().SerializeCorpus(
-            std::get<internal::InRegexpImpl::DFAPath>(astnode.children)));
+        CheckASTNodeTypeIdAndChildType<DFAPath>(astnode, id), "Invalid node!");
+    return WrapASTIntoIRObject(astnode,
+                               GetInnerRegexpDomain().SerializeCorpus(
+                                   std::get<DFAPath>(astnode.children)));
   }
 
   static std::optional<ASTNode> ParseCorpus(const IRObject& obj) {
-    if (!CheckASTCorpusStructure<internal::InRegexpImpl::DFAPath>(obj)) {
+    if (!CheckASTCorpusStructure<DFAPath>(obj)) {
       return std::nullopt;
     }
     auto subs = obj.Subs();
@@ -185,7 +182,7 @@
     if (!path) {
       return std::nullopt;
     }
-    result.children.emplace<internal::InRegexpImpl::DFAPath>(*path);
+    result.children.emplace<DFAPath>(*path);
     return result;
   }
 
@@ -636,11 +633,11 @@
     absl::flat_hash_map<ASTTypeId, std::vector<ASTNode*>>& groups);
 
 template <typename TopDomain>
-class InGrammarImpl : public DomainBase<InGrammarImpl<TopDomain>, std::string> {
+class InGrammarImpl
+    : public DomainBase<InGrammarImpl<TopDomain>, std::string, ASTNode> {
  public:
-  using value_type = std::string;
-  using corpus_type = ASTNode;
-  static constexpr bool has_custom_corpus_type = true;
+  using typename InGrammarImpl::DomainBase::corpus_type;
+  using typename InGrammarImpl::DomainBase::value_type;
 
   ASTNode Init(absl::BitGenRef prng) { return TopDomain::Init(prng); }
 
diff --git a/fuzztest/internal/domains/in_range_impl.h b/fuzztest/internal/domains/in_range_impl.h
index 9ff0198..9757131 100644
--- a/fuzztest/internal/domains/in_range_impl.h
+++ b/fuzztest/internal/domains/in_range_impl.h
@@ -36,7 +36,8 @@
 template <typename T>
 class InRangeImpl : public DomainBase<InRangeImpl<T>> {
  public:
-  using value_type = T;
+  using typename InRangeImpl::DomainBase::value_type;
+
   constexpr static bool T_is_integer = std::numeric_limits<T>::is_integer;
   constexpr static bool T_is_signed = std::is_signed<T>::value;
   constexpr static bool T_is_memory_dictionary_compatible =
diff --git a/fuzztest/internal/domains/in_regexp_impl.h b/fuzztest/internal/domains/in_regexp_impl.h
index f5afe1a..2790e79 100644
--- a/fuzztest/internal/domains/in_regexp_impl.h
+++ b/fuzztest/internal/domains/in_regexp_impl.h
@@ -34,14 +34,10 @@
 
 namespace fuzztest::internal {
 
-class InRegexpImpl : public DomainBase<InRegexpImpl, std::string> {
+using DFAPath = std::vector<RegexpDFA::Edge>;
+
+class InRegexpImpl : public DomainBase<InRegexpImpl, std::string, DFAPath> {
  public:
-  using DFAPath = std::vector<RegexpDFA::Edge>;
-  using value_type = std::string;
-  using corpus_type = DFAPath;
-
-  static constexpr bool has_custom_corpus_type = true;
-
   explicit InRegexpImpl(std::string_view regex_str)
       : dfa_(RegexpDFA::Create(regex_str)) {}
 
diff --git a/fuzztest/internal/domains/map_impl.h b/fuzztest/internal/domains/map_impl.h
index ee0f7d1..ca2958d 100644
--- a/fuzztest/internal/domains/map_impl.h
+++ b/fuzztest/internal/domains/map_impl.h
@@ -30,15 +30,13 @@
 namespace fuzztest::internal {
 
 template <typename Mapper, typename... Inner>
-class MapImpl
-    : public DomainBase<MapImpl<Mapper, Inner...>,
-                        std::decay_t<std::invoke_result_t<
-                            Mapper, const typename Inner::value_type&...>>> {
+class MapImpl : public DomainBase<MapImpl<Mapper, Inner...>,
+                                  std::decay_t<std::invoke_result_t<
+                                      Mapper, const value_type_t<Inner>&...>>,
+                                  std::tuple<corpus_type_t<Inner>...>> {
  public:
-  using corpus_type = std::tuple<corpus_type_t<Inner>...>;
-  using value_type = std::decay_t<
-      std::invoke_result_t<Mapper, const typename Inner::value_type&...>>;
-  static constexpr bool has_custom_corpus_type = true;
+  using typename MapImpl::DomainBase::corpus_type;
+  using typename MapImpl::DomainBase::value_type;
 
   MapImpl() = default;
   explicit MapImpl(Mapper mapper, Inner... inner)
diff --git a/fuzztest/internal/domains/one_of_impl.h b/fuzztest/internal/domains/one_of_impl.h
index 30d3a63..973734e 100644
--- a/fuzztest/internal/domains/one_of_impl.h
+++ b/fuzztest/internal/domains/one_of_impl.h
@@ -33,29 +33,27 @@
 
 template <typename... Inner>
 class OneOfImpl
-    : public DomainBase<OneOfImpl<Inner...>,
-                        typename std::tuple_element_t<
-                            0, typename std::tuple<Inner...>>::value_type> {
+    : public DomainBase<
+          OneOfImpl<Inner...>,
+          value_type_t<std::tuple_element_t<0, std::tuple<Inner...>>>,
+          std::variant<corpus_type_t<Inner>...>> {
  public:
+  using typename OneOfImpl::DomainBase::corpus_type;
+  using typename OneOfImpl::DomainBase::value_type;
+
   // All value_types of inner domains must be the same. (Though note that they
   // can have different corpus_types!)
-  using value_type =
-      typename std::tuple_element_t<0,
-                                    typename std::tuple<Inner...>>::value_type;
-  static_assert(std::conjunction_v<
-                    std::is_same<value_type, typename Inner::value_type>...>,
-                "All domains in a OneOf must have the same value_type.");
-
-  static constexpr bool has_custom_corpus_type = true;
-  using corpus_type = std::variant<corpus_type_t<Inner>...>;
+  static_assert(
+      std::conjunction_v<std::is_same<value_type, value_type_t<Inner>>...>,
+      "All domains in a OneOf must have the same value_type.");
 
   explicit OneOfImpl(Inner... domains) : domains_(std::move(domains)...) {}
 
   corpus_type Init(absl::BitGenRef prng) {
     // TODO(b/191368509): Consider the cardinality of the subdomains to weight
     // them.
-    return Switch<sizeof...(Inner)>(
-        absl::Uniform(prng, size_t{}, num_domains_), [&](auto I) {
+    return Switch<kNumDomains>(
+        absl::Uniform(prng, size_t{}, kNumDomains), [&](auto I) {
           return corpus_type(std::in_place_index<I>,
                              std::get<I>(domains_).Init(prng));
         });
@@ -63,18 +61,18 @@
 
   void Mutate(corpus_type& val, absl::BitGenRef prng, bool only_shrink) {
     // Switch to another domain 1% of the time when not reducing.
-    if (num_domains_ > 1 && !only_shrink && absl::Bernoulli(prng, 0.01)) {
+    if (kNumDomains > 1 && !only_shrink && absl::Bernoulli(prng, 0.01)) {
       // Choose a different index.
-      size_t offset = absl::Uniform<size_t>(prng, 1, num_domains_);
+      size_t offset = absl::Uniform<size_t>(prng, 1, kNumDomains);
       size_t index = static_cast<size_t>(val.index());
       index += offset;
-      if (index >= num_domains_) index -= num_domains_;
-      Switch<sizeof...(Inner)>(index, [&](auto I) {
+      if (index >= kNumDomains) index -= kNumDomains;
+      Switch<kNumDomains>(index, [&](auto I) {
         auto& domain = std::get<I>(domains_);
         val.template emplace<I>(domain.Init(prng));
       });
     } else {
-      Switch<sizeof...(Inner)>(val.index(), [&](auto I) {
+      Switch<kNumDomains>(val.index(), [&](auto I) {
         auto& domain = std::get<I>(domains_);
         domain.Mutate(std::get<I>(val), prng, only_shrink);
       });
@@ -82,7 +80,7 @@
   }
 
   value_type GetValue(const corpus_type& v) const {
-    return Switch<sizeof...(Inner)>(v.index(), [&](auto I) -> value_type {
+    return Switch<kNumDomains>(v.index(), [&](auto I) -> value_type {
       auto domain = std::get<I>(domains_);
       return domain.GetValue(std::get<I>(v));
     });
@@ -98,7 +96,7 @@
       return false;
     };
 
-    ApplyIndex<sizeof...(Inner)>([&](auto... I) {
+    ApplyIndex<kNumDomains>([&](auto... I) {
       // Try them in order, break on first success.
       (try_one_corpus(I) || ...);
     });
@@ -117,11 +115,10 @@
   }
 
  private:
+  static constexpr size_t kNumDomains = sizeof...(Inner);
+  static_assert(kNumDomains > 0, "OneOf requires a non-empty list.");
+
   std::tuple<Inner...> domains_;
-  static_assert(std::tuple_size_v<decltype(domains_)> > 0,
-                "OneOf requires a non-empty list.");
-  // For ease of reading.
-  const size_t num_domains_ = sizeof...(Inner);
 };
 
 }  // namespace fuzztest::internal
diff --git a/fuzztest/internal/domains/optional_of_impl.h b/fuzztest/internal/domains/optional_of_impl.h
index 23eaf95..8651f74 100644
--- a/fuzztest/internal/domains/optional_of_impl.h
+++ b/fuzztest/internal/domains/optional_of_impl.h
@@ -34,15 +34,18 @@
 enum class OptionalPolicy { kWithNull, kWithoutNull, kAlwaysNull };
 
 template <typename T, typename InnerDomain>
-class OptionalOfImpl : public DomainBase<OptionalOfImpl<T, InnerDomain>> {
+class OptionalOfImpl
+    : public DomainBase<
+          OptionalOfImpl<T, InnerDomain>, T,
+          // `T` might be a custom optional type.
+          // We use std::variant unconditionally to make it simpler.
+          std::variant<std::monostate, corpus_type_t<InnerDomain>>> {
  public:
-  using value_type = T;
+  using typename OptionalOfImpl::DomainBase::corpus_type;
+  using typename OptionalOfImpl::DomainBase::value_type;
+
   static_assert(Requires<T>([](auto x) -> decltype(!x, *x) {}),
                 "T must be an optional type.");
-  static constexpr bool has_custom_corpus_type = true;
-  // `T` might be a custom optional type.
-  // We use std::variant unconditionally to make it simpler.
-  using corpus_type = std::variant<std::monostate, corpus_type_t<InnerDomain>>;
 
   explicit OptionalOfImpl(InnerDomain inner)
       : inner_(std::move(inner)), policy_(OptionalPolicy::kWithNull) {}
diff --git a/fuzztest/internal/domains/protobuf_domain_impl.h b/fuzztest/internal/domains/protobuf_domain_impl.h
index 9bbf405..a245df7 100644
--- a/fuzztest/internal/domains/protobuf_domain_impl.h
+++ b/fuzztest/internal/domains/protobuf_domain_impl.h
@@ -400,15 +400,15 @@
 template <typename Message>
 class ProtobufDomainUntypedImpl
     : public DomainBase<ProtobufDomainUntypedImpl<Message>,
-                        std::unique_ptr<Message>> {
+                        std::unique_ptr<Message>,
+                        absl::flat_hash_map<int, GenericDomainCorpusType>> {
   using Descriptor = ProtobufDescriptor<Message>;
   using FieldDescriptor = ProtobufFieldDescriptor<Message>;
   using OneofDescriptor = ProtobufOneofDescriptor<Message>;
 
  public:
-  using corpus_type = absl::flat_hash_map<int, GenericDomainCorpusType>;
-  using value_type = std::unique_ptr<Message>;
-  static constexpr bool has_custom_corpus_type = true;
+  using typename ProtobufDomainUntypedImpl::DomainBase::corpus_type;
+  using typename ProtobufDomainUntypedImpl::DomainBase::value_type;
 
   explicit ProtobufDomainUntypedImpl(PrototypePtr<Message> prototype)
       : prototype_(std::move(prototype)),
@@ -747,7 +747,7 @@
     template <typename T>
     bool VisitSingular(const FieldDescriptor* field) {
       auto& domain = self.GetSubDomain<T, false>(field);
-      typename std::decay_t<decltype(domain)>::value_type inner_value;
+      value_type_t<std::decay_t<decltype(domain)>> inner_value;
       auto* reflection = message.GetReflection();
       if constexpr (std::is_same_v<T, ProtoMessageTag>) {
         const auto& child = reflection->GetMessage(message, field);
@@ -768,7 +768,7 @@
     template <typename T>
     bool VisitRepeated(const FieldDescriptor* field) {
       auto& domain = self.GetSubDomain<T, true>(field);
-      typename std::decay_t<decltype(domain)>::value_type inner_value;
+      value_type_t<std::decay_t<decltype(domain)>> inner_value;
       auto* reflection = message.GetReflection();
       const int size = reflection->FieldSize(message, field);
       for (int i = 0; i < size; ++i) {
@@ -1281,8 +1281,7 @@
   auto GetDomainForField(const FieldDescriptor* field,
                          bool use_policy = true) const {
     auto base_domain = GetBaseDomainForFieldType<T>(field, use_policy);
-    using field_cpptype =
-        typename std::decay_t<decltype(base_domain)>::value_type;
+    using field_cpptype = value_type_t<std::decay_t<decltype(base_domain)>>;
     if constexpr (is_repeated) {
       return Domain<std::vector<field_cpptype>>(
           GetOuterDomainForField<is_repeated>(field, base_domain, use_policy));
@@ -1416,15 +1415,14 @@
 // Domain for `T` where `T` is a Protobuf message type.
 // It is a small wrapper around `ProtobufDomainUntypedImpl` to make its API more
 // convenient.
-template <typename T>
-class ProtobufDomainImpl : public DomainBase<ProtobufDomainImpl<T>> {
-  using Inner = ProtobufDomainUntypedImpl<typename T::Message>;
-
+template <typename T,
+          typename UntypedImpl = ProtobufDomainUntypedImpl<typename T::Message>>
+class ProtobufDomainImpl
+    : public DomainBase<ProtobufDomainImpl<T>, T, corpus_type_t<UntypedImpl>> {
  public:
-  using value_type = T;
-  using corpus_type = typename Inner::corpus_type;
+  using typename ProtobufDomainImpl::DomainBase::corpus_type;
+  using typename ProtobufDomainImpl::DomainBase::value_type;
   using FieldDescriptor = ProtobufFieldDescriptor<typename T::Message>;
-  static constexpr bool has_custom_corpus_type = true;
 
   corpus_type Init(absl::BitGenRef prng) { return inner_.Init(prng); }
 
@@ -1813,11 +1811,11 @@
   Domain<std::unique_ptr<typename T::Message>> ToUntypedProtoDomain(
       Inner inner_domain) {
     return internal::MapImpl<std::function<std::unique_ptr<typename T::Message>(
-                                 typename Inner::value_type)>,
+                                 value_type_t<Inner>)>,
                              Inner>(
-        [](typename Inner::value_type proto_message)
+        [](value_type_t<Inner> proto_message)
             -> std::unique_ptr<typename T::Message> {
-          return {std::make_unique<typename Inner::value_type>(proto_message)};
+          return {std::make_unique<value_type_t<Inner>>(proto_message)};
         },
         std::move(inner_domain));
   }
@@ -1827,9 +1825,9 @@
   ToOptionalUntypedProtoDomain(Inner inner_domain) {
     return internal::MapImpl<
         std::function<std::optional<std::unique_ptr<typename T::Message>>(
-            typename Inner::value_type)>,
+            value_type_t<Inner>)>,
         Inner>(
-        [](typename Inner::value_type proto_message)
+        [](value_type_t<Inner> proto_message)
             -> std::optional<std::unique_ptr<typename T::Message>> {
           if (!proto_message.has_value()) return std::nullopt;
           return {std::make_unique<
@@ -1844,9 +1842,9 @@
   ToRepeatedUntypedProtoDomain(Inner inner_domain) {
     return internal::MapImpl<
         std::function<std::vector<std::unique_ptr<typename T::Message>>(
-            typename Inner::value_type)>,
+            value_type_t<Inner>)>,
         Inner>(
-        [](typename Inner::value_type proto_message)
+        [](value_type_t<Inner> proto_message)
             -> std::vector<std::unique_ptr<typename T::Message>> {
           std::vector<std::unique_ptr<typename T::Message>> result;
           for (auto& entry : proto_message) {
@@ -1859,7 +1857,7 @@
         std::move(inner_domain));
   }
 
-  Inner inner_{&T::default_instance()};
+  UntypedImpl inner_{&T::default_instance()};
 };
 
 template <typename T>
@@ -1870,7 +1868,7 @@
 class ArbitraryImpl<T, std::enable_if_t<is_protocol_buffer_enum_v<T>>>
     : public DomainBase<ArbitraryImpl<T>> {
  public:
-  using value_type = T;
+  using typename ArbitraryImpl::DomainBase::value_type;
 
   value_type Init(absl::BitGenRef prng) {
     const int index = absl::Uniform(prng, 0, descriptor()->value_count());
diff --git a/fuzztest/internal/domains/smart_pointer_of_impl.h b/fuzztest/internal/domains/smart_pointer_of_impl.h
index 0977025..61993e3 100644
--- a/fuzztest/internal/domains/smart_pointer_of_impl.h
+++ b/fuzztest/internal/domains/smart_pointer_of_impl.h
@@ -28,20 +28,21 @@
 
 namespace fuzztest::internal {
 
-template <typename T, typename Inner>
-class SmartPointerOfImpl : public DomainBase<SmartPointerOfImpl<T, Inner>> {
-  // We use the type erased version here to allow for recursion in smart pointer
-  // domains.
-  // It helps cut the recursion in type traits (like corpus_type) and the
-  // indirection avoids having the domain contain itself by value.
-  using RealInner = Domain<typename T::element_type>;
+template <typename T, typename Inner,
+          // We use the type erased version here to allow for recursion in smart
+          // pointer domains. It helps cut the recursion in type traits (like
+          // corpus_type) and the indirection avoids having the domain contain
+          // itself by value.
+          typename RealInner = Domain<typename T::element_type>>
+class SmartPointerOfImpl
+    : public DomainBase<
+          SmartPointerOfImpl<T, Inner>, T,
+          std::variant<std::monostate, corpus_type_t<RealInner>>> {
   using InnerFn = const RealInner& (*)();
 
  public:
-  using value_type = T;
-  static constexpr bool has_custom_corpus_type = true;
-  using corpus_type =
-      std::variant<std::monostate, typename RealInner::corpus_type>;
+  using typename SmartPointerOfImpl::DomainBase::corpus_type;
+  using typename SmartPointerOfImpl::DomainBase::value_type;
 
   // Since we allow for recursion in this domain, we want to delay the
   // construction of the inner domain. Otherwise we would have an infinite
diff --git a/fuzztest/internal/domains/unique_elements_container_of_impl.h b/fuzztest/internal/domains/unique_elements_container_of_impl.h
index 73b80f3..c4789f7 100644
--- a/fuzztest/internal/domains/unique_elements_container_of_impl.h
+++ b/fuzztest/internal/domains/unique_elements_container_of_impl.h
@@ -26,6 +26,13 @@
 
 namespace fuzztest::internal {
 
+template <typename InnerDomain>
+using UniqueDomainValueT = absl::flat_hash_set<value_type_t<InnerDomain>>;
+
+template <typename InnerDomain>
+using UniqueDomain =
+    AssociativeContainerOfImpl<UniqueDomainValueT<InnerDomain>, InnerDomain>;
+
 // UniqueElementsContainerImpl supports producing containers of type `T`, with
 // elements of type `E` from domain `InnerDomain inner`, with a guarantee that
 // each element of the container has a unique value from `InnerDomain`. The
@@ -33,16 +40,14 @@
 // which is (effectively) produced by `UnorderedSetOf(inner)`.
 template <typename T, typename InnerDomain>
 class UniqueElementsContainerImpl
-    : public DomainBase<UniqueElementsContainerImpl<T, InnerDomain>> {
-  using UniqueDomainValueT =
-      absl::flat_hash_set<typename InnerDomain::value_type>;
-  using UniqueDomain =
-      AssociativeContainerOfImpl<UniqueDomainValueT, InnerDomain>;
+    : public DomainBase<UniqueElementsContainerImpl<T, InnerDomain>, T,
+                        corpus_type_t<UniqueDomain<InnerDomain>>> {
+  using UniqueDomainValueT = UniqueDomainValueT<InnerDomain>;
+  using UniqueDomain = UniqueDomain<InnerDomain>;
 
  public:
-  using value_type = T;
-  using corpus_type = typename UniqueDomain::corpus_type;
-  static constexpr bool has_custom_corpus_type = true;
+  using typename UniqueElementsContainerImpl::DomainBase::corpus_type;
+  using typename UniqueElementsContainerImpl::DomainBase::value_type;
 
   UniqueElementsContainerImpl() = default;
   explicit UniqueElementsContainerImpl(InnerDomain inner)
@@ -64,7 +69,7 @@
 
   std::optional<corpus_type> FromValue(const value_type& v) const {
     return unique_domain_.FromValue(
-        typename UniqueDomain::value_type(v.begin(), v.end()));
+        value_type_t<UniqueDomain>(v.begin(), v.end()));
   }
 
   auto GetPrinter() const { return unique_domain_.GetPrinter(); }
diff --git a/fuzztest/internal/domains/variant_of_impl.h b/fuzztest/internal/domains/variant_of_impl.h
index 67a8640..c747f68 100644
--- a/fuzztest/internal/domains/variant_of_impl.h
+++ b/fuzztest/internal/domains/variant_of_impl.h
@@ -32,13 +32,14 @@
 namespace fuzztest::internal {
 
 template <typename T, typename... Inner>
-class VariantOfImpl : public DomainBase<VariantOfImpl<T, Inner...>> {
+class VariantOfImpl : public DomainBase<VariantOfImpl<T, Inner...>, T,
+                                        // `T` might be a custom variant type.
+                                        // We use std::variant unconditionally
+                                        // to make it simpler.
+                                        std::variant<corpus_type_t<Inner>...>> {
  public:
-  using value_type = T;
-  static constexpr bool has_custom_corpus_type = true;
-  // `T` might be a custom variant type.
-  // We use std::variant unconditionally to make it simpler.
-  using corpus_type = std::variant<corpus_type_t<Inner>...>;
+  using typename VariantOfImpl::DomainBase::corpus_type;
+  using typename VariantOfImpl::DomainBase::value_type;
 
   VariantOfImpl() = default;
   explicit VariantOfImpl(std::in_place_t, Inner... inner)
diff --git a/fuzztest/internal/fixture_driver.h b/fuzztest/internal/fixture_driver.h
index 6bc904b..fc55add 100644
--- a/fuzztest/internal/fixture_driver.h
+++ b/fuzztest/internal/fixture_driver.h
@@ -152,7 +152,7 @@
           (fixture_.get()->*target_function_)(
               ForceVectorForStringView<Args>(std::move(args))...);
         },
-        args_untyped.GetAs<typename DomainT::value_type>());
+        args_untyped.GetAs<value_type_t<DomainT>>());
   }
 
  protected:
@@ -185,7 +185,7 @@
         [&](auto&&... args) {
           target_function_(ForceVectorForStringView<Args>(std::move(args))...);
         },
-        args_untyped.GetAs<typename DomainT::value_type>());
+        args_untyped.GetAs<value_type_t<DomainT>>());
   }
 
  private:
diff --git a/fuzztest/internal/registration.h b/fuzztest/internal/registration.h
index 344d6b5..93c7168 100644
--- a/fuzztest/internal/registration.h
+++ b/fuzztest/internal/registration.h
@@ -122,10 +122,9 @@
   // void MyProperty(std::string s, int i) { ... }
   // FUZZ_TEST(MySuite, MyProperty).WithDomains(StringAndIndex(10));
   template <typename... NewDomains>
-  auto WithDomains(
-      AggregateOfImpl<std::tuple<typename NewDomains::value_type...>,
-                      RequireCustomCorpusType::kNo, NewDomains...>
-          domain) && {
+  auto WithDomains(AggregateOfImpl<std::tuple<value_type_t<NewDomains>...>,
+                                   RequireCustomCorpusType::kNo, NewDomains...>
+                       domain) && {
     static_assert(!Registration::kHasDomain,
                   "WithDomains can only be called once.");
     static_assert(!Registration::kHasSeeds,
@@ -134,8 +133,7 @@
         Base::kNumArgs == sizeof...(NewDomains),
         "Number of domains specified in .WithDomains() does not match "
         "the number of function parameters.");
-    using NewBase =
-        RegistrationWithDomainsBase<typename NewDomains::value_type...>;
+    using NewBase = RegistrationWithDomainsBase<value_type_t<NewDomains>...>;
     return Registration<Fixture, TargetFunction, NewBase>(
         test_info_, target_function_, NewBase{std::move(domain)});
   }
diff --git a/fuzztest/internal/table_of_recent_compares.h b/fuzztest/internal/table_of_recent_compares.h
index 659dc39..2ec622c 100644
--- a/fuzztest/internal/table_of_recent_compares.h
+++ b/fuzztest/internal/table_of_recent_compares.h
@@ -28,6 +28,7 @@
 #include "absl/container/flat_hash_set.h"
 #include "absl/random/bit_gen_ref.h"
 #include "absl/random/distributions.h"
+#include "./fuzztest/internal/type_support.h"
 
 namespace fuzztest::internal {
 
@@ -205,7 +206,7 @@
   template <typename ContainerT>
   std::vector<DictionaryEntry<ContainerT>>
   GetMatchingContainerDictionaryEntries(const ContainerT& val) const {
-    using T = typename ContainerT::value_type;
+    using T = value_type_t<ContainerT>;
     static_assert(
         sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8,
         "GetMatchingDictionaryEntries only accepts basic"
@@ -236,7 +237,7 @@
   GetMatchingContainerDictionaryEntry(const ContainerT& val,
                                       const uint8_t* buf1, const uint8_t* buf2,
                                       size_t buf_size) {
-    using T = typename ContainerT::value_type;
+    using T = value_type_t<ContainerT>;
     size_t val_size = val.size() * sizeof(T);
     static constexpr size_t kBufSizeValueMask = sizeof(T) - 1;
 
@@ -273,7 +274,7 @@
   static std::optional<DictionaryEntry<ContainerT>> GetRandomSide(
       absl::BitGenRef prng, const uint8_t* buf1, const uint8_t* buf2,
       size_t buf_size) {
-    using T = typename ContainerT::value_type;
+    using T = value_type_t<ContainerT>;
     static constexpr size_t kBufSizeValueMask = sizeof(T) - 1;
     if ((buf_size & kBufSizeValueMask) != 0 || buf_size == 0) {
       return std::nullopt;
@@ -288,7 +289,7 @@
   }
 
  private:
-  template <typename ContainerT, typename T = typename ContainerT::value_type>
+  template <typename ContainerT, typename T = value_type_t<ContainerT>>
   static ContainerT MakeContainer(const uint8_t* buf, size_t buf_size) {
     ContainerT result = {};
     std::copy(reinterpret_cast<const T*>(buf),
@@ -385,11 +386,11 @@
 
 template <typename ContainerT>
 class ContainerDictionary {
-  static_assert(std::is_integral_v<typename ContainerT::value_type> &&
-                    (sizeof(typename ContainerT::value_type) == 1 ||
-                     sizeof(typename ContainerT::value_type) == 2 ||
-                     sizeof(typename ContainerT::value_type) == 4 ||
-                     sizeof(typename ContainerT::value_type) == 8),
+  static_assert(std::is_integral_v<value_type_t<ContainerT>> &&
+                    (sizeof(value_type_t<ContainerT>) == 1 ||
+                     sizeof(value_type_t<ContainerT>) == 2 ||
+                     sizeof(value_type_t<ContainerT>) == 4 ||
+                     sizeof(value_type_t<ContainerT>) == 8),
                 "ContainerDictionary only accepts container::value_type being "
                 "basic types with size = "
                 "{1, 2, 4, 8}.");
@@ -415,7 +416,7 @@
   static std::optional<DictionaryEntry<ContainerT>> GetRandomTORCEntry(
       const ContainerT& val, absl::BitGenRef prng,
       const TablesOfRecentCompares& torc) {
-    using T = typename ContainerT::value_type;
+    using T = value_type_t<ContainerT>;
     std::optional<DictionaryEntry<ContainerT>> result = std::nullopt;
     // Get from mem_cmp_table or i*_cmp_table with 50/50 probability.
     if (RandomBool(prng)) {
@@ -492,7 +493,7 @@
   // `GetMatchingContainerDictionaryEntry` to find matches in `val`.
   void AddMatchingIntegerDictionaryEntriesFromTORC(
       const ContainerT& val, const TablesOfRecentCompares& torc) {
-    using T = typename ContainerT::value_type;
+    using T = value_type_t<ContainerT>;
     if constexpr (sizeof(T) <= 4) {
       if (val.size() >= 4) {
         for (auto& i : torc.Get<4>().GetTable()) {
diff --git a/fuzztest/internal/type_support.h b/fuzztest/internal/type_support.h
index 4ecdce6..d859b11 100644
--- a/fuzztest/internal/type_support.h
+++ b/fuzztest/internal/type_support.h
@@ -37,22 +37,11 @@
 
 namespace fuzztest::internal {
 
-template <typename Domain, typename = void>
-struct DomainTraitsImpl {
-  using corpus_type = typename Domain::value_type;
-};
+template <typename Domain>
+using value_type_t = typename Domain::value_type;
 
 template <typename Domain>
-struct DomainTraitsImpl<
-    Domain, std::enable_if_t<Domain::has_custom_corpus_type,
-                             std::void_t<typename Domain::corpus_type>>> {
-  using corpus_type = typename Domain::corpus_type;
-};
-
-// If `has_custom_corpus_type_v<Domain>`, `Domain::corpus_type`.
-// Otherwise `Domain::value_type`.
-template <typename Domain>
-using corpus_type_t = typename DomainTraitsImpl<Domain>::corpus_type;
+using corpus_type_t = typename Domain::corpus_type;
 
 // Return a best effort printer for type `T`.
 // This is useful for cases where the domain can't figure out how to print the
@@ -249,7 +238,7 @@
 
   void PrintCorpusValue(const corpus_type_t<Domain>& v, RawSink out,
                         PrintMode mode) const {
-    using value_type = typename Domain::value_type;
+    using value_type = value_type_t<Domain>;
     constexpr bool is_pointer = Requires<value_type>(
         [](auto probe)
             -> std::enable_if_t<std::is_pointer_v<decltype(probe.get())>> {});
diff --git a/fuzztest/internal/type_support_test.cc b/fuzztest/internal/type_support_test.cc
index edb1ef2..2a08ef5 100644
--- a/fuzztest/internal/type_support_test.cc
+++ b/fuzztest/internal/type_support_test.cc
@@ -264,9 +264,9 @@
   auto color_domain = ElementOf({kBlue});
   auto print = [&](auto v, auto domain) {
     // We have to create the inner corpus_type of Domain here.
-    return TestPrintValue(typename decltype(domain)::corpus_type(
-                              std::in_place_type<decltype(v)>, v),
-                          domain);
+    return TestPrintValue(
+        corpus_type_t<decltype(domain)>(std::in_place_type<decltype(v)>, v),
+        domain);
   };
   EXPECT_THAT(print('a', Domain<char>(Arbitrary<char>())),
               ElementsAre("'a' (97)", "'a'"));