traits_bag.h 10 KB

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