xref: /aosp_15_r20/external/cronet/base/traits_bag.h (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2018 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef BASE_TRAITS_BAG_H_
6 #define BASE_TRAITS_BAG_H_
7 
8 #include <initializer_list>
9 #include <optional>
10 #include <tuple>
11 #include <type_traits>
12 #include <utility>
13 
14 #include "base/parameter_pack.h"
15 
16 // A bag of Traits (structs / enums / etc...) can be an elegant alternative to
17 // the builder pattern and multiple default arguments for configuring things.
18 // Traits are terser than the builder pattern and can be evaluated at compile
19 // time, however they require the use of variadic templates which complicates
20 // matters. This file contains helpers that make Traits easier to use.
21 //
22 // WARNING: Trait bags are currently too heavy for non-constexpr usage in prod
23 // code due to template bloat, although adding NOINLINE to template constructors
24 // configured via trait bags can help.
25 //
26 // E.g.
27 //   struct EnableFeatureX {};
28 //   struct UnusedTrait {};
29 //   enum Color { RED, BLUE };
30 //
31 //   struct ValidTraits {
32 //      ValidTraits(EnableFeatureX);
33 //      ValidTraits(Color);
34 //   };
35 //   ...
36 //   DoSomethingAwesome();                 // Use defaults (Color::BLUE &
37 //                                         // feature X not enabled)
38 //   DoSomethingAwesome(EnableFeatureX(),  // Turn feature X on
39 //                      Color::RED);       // And make it red.
40 //   DoSomethingAwesome(UnusedTrait(),     // Compile time error.
41 //                      Color::RED);
42 //
43 // DoSomethingAwesome might be defined as:
44 //
45 //   template <class... ArgTypes>
46 //     requires trait_helpers::AreValidTraits<ValidTraits, ArgTypes...>
47 //   constexpr void DoSomethingAwesome(ArgTypes... args)
48 //      : enable_feature_x(
49 //            trait_helpers::HasTrait<EnableFeatureX, ArgTypes...>()),
50 //        color(trait_helpers::GetEnum<Color, EnumTraitA::BLUE>(args...)) {}
51 
52 namespace base {
53 namespace trait_helpers {
54 
55 // Represents a trait that has been removed by a predicate.
56 struct EmptyTrait {};
57 
58 // Predicate used to remove any traits from the given list of types by
59 // converting them to EmptyTrait. E.g.
60 //
61 // template <typename... Args>
62 // void MyFunc(Args... args) {
63 //   DoSomethingWithTraits(
64 //       base::trait_helpers::Exclude<UnwantedTrait1,
65 //                                    UnwantedTrait2>::Filter(args)...);
66 // }
67 //
68 // NB It's possible to actually remove the unwanted trait from the pack, but
69 // that requires constructing a filtered tuple and applying it to the function,
70 // which isn't worth the complexity over ignoring EmptyTrait.
71 template <typename... TraitsToExclude>
72 struct Exclude {
73   template <typename T>
FilterExclude74   static constexpr auto Filter(T t) {
75     if constexpr (ParameterPack<TraitsToExclude...>::template HasType<
76                       T>::value) {
77       return EmptyTrait();
78     } else {
79       return t;
80     }
81   }
82 };
83 
84 // CallFirstTag is an argument tag that helps to avoid ambiguous overloaded
85 // functions. When the following call is made:
86 //    func(CallFirstTag(), arg...);
87 // the compiler will give precedence to an overload candidate that directly
88 // takes CallFirstTag. Another overload that takes CallSecondTag will be
89 // considered iff the preferred overload candidates were all invalids and
90 // therefore discarded.
91 struct CallSecondTag {};
92 struct CallFirstTag : CallSecondTag {};
93 
94 // A trait filter class |TraitFilterType| implements the protocol to get a value
95 // of type |ArgType| from an argument list and convert it to a value of type
96 // |TraitType|. If the argument list contains an argument of type |ArgType|, the
97 // filter class will be instantiated with that argument. If the argument list
98 // contains no argument of type |ArgType|, the filter class will be instantiated
99 // using the default constructor if available; a compile error is issued
100 // otherwise. The filter class must have the conversion operator TraitType()
101 // which returns a value of type TraitType.
102 
103 // |InvalidTrait| is used to return from GetTraitFromArg when the argument is
104 // not compatible with the desired trait.
105 struct InvalidTrait {};
106 
107 // Returns an object of type |TraitFilterType| constructed from |arg| if
108 // compatible, or |InvalidTrait| otherwise.
109 template <class TraitFilterType, class ArgType>
110   requires std::constructible_from<TraitFilterType, ArgType>
GetTraitFromArg(CallFirstTag,ArgType arg)111 constexpr TraitFilterType GetTraitFromArg(CallFirstTag, ArgType arg) {
112   return TraitFilterType(arg);
113 }
114 
115 template <class TraitFilterType, class ArgType>
GetTraitFromArg(CallSecondTag,ArgType arg)116 constexpr InvalidTrait GetTraitFromArg(CallSecondTag, ArgType arg) {
117   return InvalidTrait();
118 }
119 
120 // Returns an object of type |TraitFilterType| constructed from a compatible
121 // argument in |args...|, or default constructed if none of the arguments are
122 // compatible. This is the implementation of GetTraitFromArgList() with a
123 // disambiguation tag.
124 template <class TraitFilterType, class... ArgTypes>
125   requires(std::constructible_from<TraitFilterType, ArgTypes> || ...)
GetTraitFromArgListImpl(CallFirstTag,ArgTypes...args)126 constexpr TraitFilterType GetTraitFromArgListImpl(CallFirstTag,
127                                                   ArgTypes... args) {
128   return std::get<TraitFilterType>(std::make_tuple(
129       GetTraitFromArg<TraitFilterType>(CallFirstTag(), args)...));
130 }
131 
132 template <class TraitFilterType, class... ArgTypes>
GetTraitFromArgListImpl(CallSecondTag,ArgTypes...args)133 constexpr TraitFilterType GetTraitFromArgListImpl(CallSecondTag,
134                                                   ArgTypes... args) {
135   static_assert(std::is_constructible_v<TraitFilterType>,
136                 "The traits bag is missing a required trait.");
137   return TraitFilterType();
138 }
139 
140 // Constructs an object of type |TraitFilterType| from a compatible argument in
141 // |args...|, or using the default constructor, and returns its associated trait
142 // value using conversion to |TraitFilterType::ValueType|. If there are more
143 // than one compatible argument in |args|, generates a compile-time error.
144 template <class TraitFilterType, class... ArgTypes>
GetTraitFromArgList(ArgTypes...args)145 constexpr typename TraitFilterType::ValueType GetTraitFromArgList(
146     ArgTypes... args) {
147   static_assert(
148       count({std::is_constructible_v<TraitFilterType, ArgTypes>...}, true) <= 1,
149       "The traits bag contains multiple traits of the same type.");
150   return GetTraitFromArgListImpl<TraitFilterType>(CallFirstTag(), args...);
151 }
152 
153 // Helper class to implemnent a |TraitFilterType|.
154 template <typename T, typename _ValueType = T>
155 struct BasicTraitFilter {
156   using ValueType = _ValueType;
157 
BasicTraitFilterBasicTraitFilter158   constexpr BasicTraitFilter(ValueType v) : value(v) {}
159 
ValueTypeBasicTraitFilter160   constexpr operator ValueType() const { return value; }
161 
162   ValueType value = {};
163 };
164 
165 template <typename ArgType, ArgType DefaultValue>
166 struct EnumTraitFilter : public BasicTraitFilter<ArgType> {
EnumTraitFilterEnumTraitFilter167   constexpr EnumTraitFilter() : BasicTraitFilter<ArgType>(DefaultValue) {}
EnumTraitFilterEnumTraitFilter168   constexpr EnumTraitFilter(ArgType arg) : BasicTraitFilter<ArgType>(arg) {}
169 };
170 
171 template <typename ArgType>
172 struct OptionalEnumTraitFilter
173     : public BasicTraitFilter<ArgType, std::optional<ArgType>> {
OptionalEnumTraitFilterOptionalEnumTraitFilter174   constexpr OptionalEnumTraitFilter()
175       : BasicTraitFilter<ArgType, std::optional<ArgType>>(std::nullopt) {}
OptionalEnumTraitFilterOptionalEnumTraitFilter176   constexpr OptionalEnumTraitFilter(ArgType arg)
177       : BasicTraitFilter<ArgType, std::optional<ArgType>>(arg) {}
178 };
179 
180 // Tests whether multiple given argtument types are all valid traits according
181 // to the provided ValidTraits. To use, define a ValidTraits
182 template <typename ArgType>
183 struct RequiredEnumTraitFilter : public BasicTraitFilter<ArgType> {
RequiredEnumTraitFilterRequiredEnumTraitFilter184   constexpr RequiredEnumTraitFilter(ArgType arg)
185       : BasicTraitFilter<ArgType>(arg) {}
186 };
187 
188 // Note EmptyTrait is always regarded as valid to support filtering.
189 template <class ValidTraits, class T>
190 concept IsValidTrait =
191     std::constructible_from<ValidTraits, T> || std::same_as<T, EmptyTrait>;
192 
193 // Tests whether a given trait type is valid or invalid by testing whether it is
194 // convertible to the provided ValidTraits type. To use, define a ValidTraits
195 // type like this:
196 //
197 // struct ValidTraits {
198 //   ValidTraits(MyTrait);
199 //   ...
200 // };
201 //
202 // You can 'inherit' valid traits like so:
203 //
204 // struct MoreValidTraits {
205 //   MoreValidTraits(ValidTraits);  // Pull in traits from ValidTraits.
206 //   MoreValidTraits(MyOtherTrait);
207 //   ...
208 // };
209 template <class ValidTraits, class... ArgTypes>
210 concept AreValidTraits = (IsValidTrait<ValidTraits, ArgTypes> && ...);
211 
212 // Helper to make getting an enum from a trait more readable.
213 template <typename Enum, typename... Args>
GetEnum(Args...args)214 static constexpr Enum GetEnum(Args... args) {
215   return GetTraitFromArgList<RequiredEnumTraitFilter<Enum>>(args...);
216 }
217 
218 // Helper to make getting an enum from a trait with a default more readable.
219 template <typename Enum, Enum DefaultValue, typename... Args>
GetEnum(Args...args)220 static constexpr Enum GetEnum(Args... args) {
221   return GetTraitFromArgList<EnumTraitFilter<Enum, DefaultValue>>(args...);
222 }
223 
224 // Helper to make getting an optional enum from a trait with a default more
225 // readable.
226 template <typename Enum, typename... Args>
GetOptionalEnum(Args...args)227 static constexpr std::optional<Enum> GetOptionalEnum(Args... args) {
228   return GetTraitFromArgList<OptionalEnumTraitFilter<Enum>>(args...);
229 }
230 
231 // Helper to make checking for the presence of a trait more readable.
232 template <typename Trait, typename... Args>
233 struct HasTrait : ParameterPack<Args...>::template HasType<Trait> {
234   static_assert(count({std::is_constructible_v<Trait, Args>...}, true) <= 1,
235                 "The traits bag contains multiple traits of the same type.");
236 };
237 
238 // If you need a template vararg constructor to delegate to a private
239 // constructor, you may need to add this to the private constructor to ensure
240 // it's not matched by accident.
241 struct NotATraitTag {};
242 
243 }  // namespace trait_helpers
244 }  // namespace base
245 
246 #endif  // BASE_TRAITS_BAG_H_
247