• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <tuple>
10 #include <type_traits>
11 #include <utility>
12 
13 #include "base/parameter_pack.h"
14 #include "third_party/abseil-cpp/absl/types/optional.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 //             class CheckArgumentsAreValid = std::enable_if_t<
47 //                 trait_helpers::AreValidTraits<ValidTraits,
48 //                                               ArgTypes...>::value>>
49 //   constexpr void DoSomethingAwesome(ArgTypes... args)
50 //      : enable_feature_x(
51 //            trait_helpers::HasTrait<EnableFeatureX, ArgTypes...>()),
52 //        color(trait_helpers::GetEnum<Color, EnumTraitA::BLUE>(args...)) {}
53 
54 namespace base {
55 namespace trait_helpers {
56 
57 // Represents a trait that has been removed by a predicate.
58 struct EmptyTrait {};
59 
60 // Predicate used to remove any traits from the given list of types by
61 // converting them to EmptyTrait. E.g.
62 //
63 // template <typename... Args>
64 // void MyFunc(Args... args) {
65 //   DoSomethingWithTraits(
66 //       base::trait_helpers::Exclude<UnwantedTrait1,
67 //                                    UnwantedTrait2>::Filter(args)...);
68 // }
69 //
70 // NB It's possible to actually remove the unwanted trait from the pack, but
71 // that requires constructing a filtered tuple and applying it to the function,
72 // which isn't worth the complexity over ignoring EmptyTrait.
73 template <typename... TraitsToExclude>
74 struct Exclude {
75   template <typename T>
FilterExclude76   static constexpr auto Filter(T t) {
77     if constexpr (ParameterPack<TraitsToExclude...>::template HasType<
78                       T>::value) {
79       return EmptyTrait();
80     } else {
81       return t;
82     }
83   }
84 };
85 
86 // CallFirstTag is an argument tag that helps to avoid ambiguous overloaded
87 // functions. When the following call is made:
88 //    func(CallFirstTag(), arg...);
89 // the compiler will give precedence to an overload candidate that directly
90 // takes CallFirstTag. Another overload that takes CallSecondTag will be
91 // considered iff the preferred overload candidates were all invalids and
92 // therefore discarded.
93 struct CallSecondTag {};
94 struct CallFirstTag : CallSecondTag {};
95 
96 // A trait filter class |TraitFilterType| implements the protocol to get a value
97 // of type |ArgType| from an argument list and convert it to a value of type
98 // |TraitType|. If the argument list contains an argument of type |ArgType|, the
99 // filter class will be instantiated with that argument. If the argument list
100 // contains no argument of type |ArgType|, the filter class will be instantiated
101 // using the default constructor if available; a compile error is issued
102 // otherwise. The filter class must have the conversion operator TraitType()
103 // which returns a value of type TraitType.
104 
105 // |InvalidTrait| is used to return from GetTraitFromArg when the argument is
106 // not compatible with the desired trait.
107 struct InvalidTrait {};
108 
109 // Returns an object of type |TraitFilterType| constructed from |arg| if
110 // compatible, or |InvalidTrait| otherwise.
111 template <class TraitFilterType,
112           class ArgType,
113           class CheckArgumentIsCompatible = std::enable_if_t<
114               std::is_constructible<TraitFilterType, ArgType>::value>>
GetTraitFromArg(CallFirstTag,ArgType arg)115 constexpr TraitFilterType GetTraitFromArg(CallFirstTag, ArgType arg) {
116   return TraitFilterType(arg);
117 }
118 
119 template <class TraitFilterType, class ArgType>
GetTraitFromArg(CallSecondTag,ArgType arg)120 constexpr InvalidTrait GetTraitFromArg(CallSecondTag, ArgType arg) {
121   return InvalidTrait();
122 }
123 
124 // Returns an object of type |TraitFilterType| constructed from a compatible
125 // argument in |args...|, or default constructed if none of the arguments are
126 // compatible. This is the implementation of GetTraitFromArgList() with a
127 // disambiguation tag.
128 template <class TraitFilterType,
129           class... ArgTypes,
130           class TestCompatibleArgument = std::enable_if_t<any_of(
131               {std::is_constructible<TraitFilterType, ArgTypes>::value...})>>
GetTraitFromArgListImpl(CallFirstTag,ArgTypes...args)132 constexpr TraitFilterType GetTraitFromArgListImpl(CallFirstTag,
133                                                   ArgTypes... args) {
134   return std::get<TraitFilterType>(std::make_tuple(
135       GetTraitFromArg<TraitFilterType>(CallFirstTag(), args)...));
136 }
137 
138 template <class TraitFilterType, class... ArgTypes>
GetTraitFromArgListImpl(CallSecondTag,ArgTypes...args)139 constexpr TraitFilterType GetTraitFromArgListImpl(CallSecondTag,
140                                                   ArgTypes... args) {
141   static_assert(std::is_constructible<TraitFilterType>::value,
142                 "The traits bag is missing a required trait.");
143   return TraitFilterType();
144 }
145 
146 // Constructs an object of type |TraitFilterType| from a compatible argument in
147 // |args...|, or using the default constructor, and returns its associated trait
148 // value using conversion to |TraitFilterType::ValueType|. If there are more
149 // than one compatible argument in |args|, generates a compile-time error.
150 template <class TraitFilterType, class... ArgTypes>
GetTraitFromArgList(ArgTypes...args)151 constexpr typename TraitFilterType::ValueType GetTraitFromArgList(
152     ArgTypes... args) {
153   static_assert(
154       count({std::is_constructible<TraitFilterType, ArgTypes>::value...},
155             true) <= 1,
156       "The traits bag contains multiple traits of the same type.");
157   return GetTraitFromArgListImpl<TraitFilterType>(CallFirstTag(), args...);
158 }
159 
160 // Helper class to implemnent a |TraitFilterType|.
161 template <typename T, typename _ValueType = T>
162 struct BasicTraitFilter {
163   using ValueType = _ValueType;
164 
BasicTraitFilterBasicTraitFilter165   constexpr BasicTraitFilter(ValueType v) : value(v) {}
166 
ValueTypeBasicTraitFilter167   constexpr operator ValueType() const { return value; }
168 
169   ValueType value = {};
170 };
171 
172 template <typename ArgType, ArgType DefaultValue>
173 struct EnumTraitFilter : public BasicTraitFilter<ArgType> {
EnumTraitFilterEnumTraitFilter174   constexpr EnumTraitFilter() : BasicTraitFilter<ArgType>(DefaultValue) {}
EnumTraitFilterEnumTraitFilter175   constexpr EnumTraitFilter(ArgType arg) : BasicTraitFilter<ArgType>(arg) {}
176 };
177 
178 template <typename ArgType>
179 struct OptionalEnumTraitFilter
180     : public BasicTraitFilter<ArgType, absl::optional<ArgType>> {
OptionalEnumTraitFilterOptionalEnumTraitFilter181   constexpr OptionalEnumTraitFilter()
182       : BasicTraitFilter<ArgType, absl::optional<ArgType>>(absl::nullopt) {}
OptionalEnumTraitFilterOptionalEnumTraitFilter183   constexpr OptionalEnumTraitFilter(ArgType arg)
184       : BasicTraitFilter<ArgType, absl::optional<ArgType>>(arg) {}
185 };
186 
187 // Tests whether multiple given argtument types are all valid traits according
188 // to the provided ValidTraits. To use, define a ValidTraits
189 template <typename ArgType>
190 struct RequiredEnumTraitFilter : public BasicTraitFilter<ArgType> {
RequiredEnumTraitFilterRequiredEnumTraitFilter191   constexpr RequiredEnumTraitFilter(ArgType arg)
192       : BasicTraitFilter<ArgType>(arg) {}
193 };
194 
195 // Note EmptyTrait is always regarded as valid to support filtering.
196 template <class ValidTraits, class T>
197 using IsValidTrait = std::disjunction<std::is_constructible<ValidTraits, T>,
198                                       std::is_same<T, EmptyTrait>>;
199 
200 // Tests whether a given trait type is valid or invalid by testing whether it is
201 // convertible to the provided ValidTraits type. To use, define a ValidTraits
202 // type like this:
203 //
204 // struct ValidTraits {
205 //   ValidTraits(MyTrait);
206 //   ...
207 // };
208 //
209 // You can 'inherit' valid traits like so:
210 //
211 // struct MoreValidTraits {
212 //   MoreValidTraits(ValidTraits);  // Pull in traits from ValidTraits.
213 //   MoreValidTraits(MyOtherTrait);
214 //   ...
215 // };
216 template <class ValidTraits, class... ArgTypes>
217 using AreValidTraits =
218     std::bool_constant<all_of({IsValidTrait<ValidTraits, ArgTypes>::value...})>;
219 
220 // Helper to make getting an enum from a trait more readable.
221 template <typename Enum, typename... Args>
GetEnum(Args...args)222 static constexpr Enum GetEnum(Args... args) {
223   return GetTraitFromArgList<RequiredEnumTraitFilter<Enum>>(args...);
224 }
225 
226 // Helper to make getting an enum from a trait with a default more readable.
227 template <typename Enum, Enum DefaultValue, typename... Args>
GetEnum(Args...args)228 static constexpr Enum GetEnum(Args... args) {
229   return GetTraitFromArgList<EnumTraitFilter<Enum, DefaultValue>>(args...);
230 }
231 
232 // Helper to make getting an optional enum from a trait with a default more
233 // readable.
234 template <typename Enum, typename... Args>
GetOptionalEnum(Args...args)235 static constexpr absl::optional<Enum> GetOptionalEnum(Args... args) {
236   return GetTraitFromArgList<OptionalEnumTraitFilter<Enum>>(args...);
237 }
238 
239 // Helper to make checking for the presence of a trait more readable.
240 template <typename Trait, typename... Args>
241 struct HasTrait : ParameterPack<Args...>::template HasType<Trait> {
242   static_assert(
243       count({std::is_constructible<Trait, Args>::value...}, true) <= 1,
244       "The traits bag contains multiple traits of the same type.");
245 };
246 
247 // If you need a template vararg constructor to delegate to a private
248 // constructor, you may need to add this to the private constructor to ensure
249 // it's not matched by accident.
250 struct NotATraitTag {};
251 
252 }  // namespace trait_helpers
253 }  // namespace base
254 
255 #endif  // BASE_TRAITS_BAG_H_
256