| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef BASE_TRAITS_BAG_H_ |
| #define BASE_TRAITS_BAG_H_ |
| |
| #include <initializer_list> |
| #include <tuple> |
| #include <type_traits> |
| #include <utility> |
| |
| #include "base/parameter_pack.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| // A bag of Traits (structs / enums / etc...) can be an elegant alternative to |
| // the builder pattern and multiple default arguments for configuring things. |
| // Traits are terser than the builder pattern and can be evaluated at compile |
| // time, however they require the use of variadic templates which complicates |
| // matters. This file contains helpers that make Traits easier to use. |
| // |
| // WARNING: Trait bags are currently too heavy for non-constexpr usage in prod |
| // code due to template bloat, although adding NOINLINE to template constructors |
| // configured via trait bags can help. |
| // |
| // E.g. |
| // struct EnableFeatureX {}; |
| // struct UnusedTrait {}; |
| // enum Color { RED, BLUE }; |
| // |
| // struct ValidTraits { |
| // ValidTraits(EnableFeatureX); |
| // ValidTraits(Color); |
| // }; |
| // ... |
| // DoSomethingAwesome(); // Use defaults (Color::BLUE & |
| // // feature X not enabled) |
| // DoSomethingAwesome(EnableFeatureX(), // Turn feature X on |
| // Color::RED); // And make it red. |
| // DoSomethingAwesome(UnusedTrait(), // Compile time error. |
| // Color::RED); |
| // |
| // DoSomethingAwesome might be defined as: |
| // |
| // template <class... ArgTypes, |
| // class CheckArgumentsAreValid = std::enable_if_t< |
| // trait_helpers::AreValidTraits<ValidTraits, |
| // ArgTypes...>::value>> |
| // constexpr void DoSomethingAwesome(ArgTypes... args) |
| // : enable_feature_x( |
| // trait_helpers::HasTrait<EnableFeatureX, ArgTypes...>()), |
| // color(trait_helpers::GetEnum<Color, EnumTraitA::BLUE>(args...)) {} |
| |
| namespace base { |
| namespace trait_helpers { |
| |
| // Represents a trait that has been removed by a predicate. |
| struct EmptyTrait {}; |
| |
| // Predicate used to remove any traits from the given list of types by |
| // converting them to EmptyTrait. E.g. |
| // |
| // template <typename... Args> |
| // void MyFunc(Args... args) { |
| // DoSomethingWithTraits( |
| // base::trait_helpers::Exclude<UnwantedTrait1, |
| // UnwantedTrait2>::Filter(args)...); |
| // } |
| // |
| // NB It's possible to actually remove the unwanted trait from the pack, but |
| // that requires constructing a filtered tuple and applying it to the function, |
| // which isn't worth the complexity over ignoring EmptyTrait. |
| template <typename... TraitsToExclude> |
| struct Exclude { |
| template <typename T> |
| static constexpr auto Filter(T t) { |
| if constexpr (ParameterPack<TraitsToExclude...>::template HasType< |
| T>::value) { |
| return EmptyTrait(); |
| } else { |
| return t; |
| } |
| } |
| }; |
| |
| // CallFirstTag is an argument tag that helps to avoid ambiguous overloaded |
| // functions. When the following call is made: |
| // func(CallFirstTag(), arg...); |
| // the compiler will give precedence to an overload candidate that directly |
| // takes CallFirstTag. Another overload that takes CallSecondTag will be |
| // considered iff the preferred overload candidates were all invalids and |
| // therefore discarded. |
| struct CallSecondTag {}; |
| struct CallFirstTag : CallSecondTag {}; |
| |
| // A trait filter class |TraitFilterType| implements the protocol to get a value |
| // of type |ArgType| from an argument list and convert it to a value of type |
| // |TraitType|. If the argument list contains an argument of type |ArgType|, the |
| // filter class will be instantiated with that argument. If the argument list |
| // contains no argument of type |ArgType|, the filter class will be instantiated |
| // using the default constructor if available; a compile error is issued |
| // otherwise. The filter class must have the conversion operator TraitType() |
| // which returns a value of type TraitType. |
| |
| // |InvalidTrait| is used to return from GetTraitFromArg when the argument is |
| // not compatible with the desired trait. |
| struct InvalidTrait {}; |
| |
| // Returns an object of type |TraitFilterType| constructed from |arg| if |
| // compatible, or |InvalidTrait| otherwise. |
| template <class TraitFilterType, |
| class ArgType, |
| class CheckArgumentIsCompatible = std::enable_if_t< |
| std::is_constructible<TraitFilterType, ArgType>::value>> |
| constexpr TraitFilterType GetTraitFromArg(CallFirstTag, ArgType arg) { |
| return TraitFilterType(arg); |
| } |
| |
| template <class TraitFilterType, class ArgType> |
| constexpr InvalidTrait GetTraitFromArg(CallSecondTag, ArgType arg) { |
| return InvalidTrait(); |
| } |
| |
| // Returns an object of type |TraitFilterType| constructed from a compatible |
| // argument in |args...|, or default constructed if none of the arguments are |
| // compatible. This is the implementation of GetTraitFromArgList() with a |
| // disambiguation tag. |
| template <class TraitFilterType, |
| class... ArgTypes, |
| class TestCompatibleArgument = std::enable_if_t<any_of( |
| {std::is_constructible<TraitFilterType, ArgTypes>::value...})>> |
| constexpr TraitFilterType GetTraitFromArgListImpl(CallFirstTag, |
| ArgTypes... args) { |
| return std::get<TraitFilterType>(std::make_tuple( |
| GetTraitFromArg<TraitFilterType>(CallFirstTag(), args)...)); |
| } |
| |
| template <class TraitFilterType, class... ArgTypes> |
| constexpr TraitFilterType GetTraitFromArgListImpl(CallSecondTag, |
| ArgTypes... args) { |
| static_assert(std::is_constructible<TraitFilterType>::value, |
| "The traits bag is missing a required trait."); |
| return TraitFilterType(); |
| } |
| |
| // Constructs an object of type |TraitFilterType| from a compatible argument in |
| // |args...|, or using the default constructor, and returns its associated trait |
| // value using conversion to |TraitFilterType::ValueType|. If there are more |
| // than one compatible argument in |args|, generates a compile-time error. |
| template <class TraitFilterType, class... ArgTypes> |
| constexpr typename TraitFilterType::ValueType GetTraitFromArgList( |
| ArgTypes... args) { |
| static_assert( |
| count({std::is_constructible<TraitFilterType, ArgTypes>::value...}, |
| true) <= 1, |
| "The traits bag contains multiple traits of the same type."); |
| return GetTraitFromArgListImpl<TraitFilterType>(CallFirstTag(), args...); |
| } |
| |
| // Helper class to implemnent a |TraitFilterType|. |
| template <typename T, typename _ValueType = T> |
| struct BasicTraitFilter { |
| using ValueType = _ValueType; |
| |
| constexpr BasicTraitFilter(ValueType v) : value(v) {} |
| |
| constexpr operator ValueType() const { return value; } |
| |
| ValueType value = {}; |
| }; |
| |
| template <typename ArgType, ArgType DefaultValue> |
| struct EnumTraitFilter : public BasicTraitFilter<ArgType> { |
| constexpr EnumTraitFilter() : BasicTraitFilter<ArgType>(DefaultValue) {} |
| constexpr EnumTraitFilter(ArgType arg) : BasicTraitFilter<ArgType>(arg) {} |
| }; |
| |
| template <typename ArgType> |
| struct OptionalEnumTraitFilter |
| : public BasicTraitFilter<ArgType, absl::optional<ArgType>> { |
| constexpr OptionalEnumTraitFilter() |
| : BasicTraitFilter<ArgType, absl::optional<ArgType>>(absl::nullopt) {} |
| constexpr OptionalEnumTraitFilter(ArgType arg) |
| : BasicTraitFilter<ArgType, absl::optional<ArgType>>(arg) {} |
| }; |
| |
| // Tests whether multiple given argtument types are all valid traits according |
| // to the provided ValidTraits. To use, define a ValidTraits |
| template <typename ArgType> |
| struct RequiredEnumTraitFilter : public BasicTraitFilter<ArgType> { |
| constexpr RequiredEnumTraitFilter(ArgType arg) |
| : BasicTraitFilter<ArgType>(arg) {} |
| }; |
| |
| // Note EmptyTrait is always regarded as valid to support filtering. |
| template <class ValidTraits, class T> |
| using IsValidTrait = std::disjunction<std::is_constructible<ValidTraits, T>, |
| std::is_same<T, EmptyTrait>>; |
| |
| // Tests whether a given trait type is valid or invalid by testing whether it is |
| // convertible to the provided ValidTraits type. To use, define a ValidTraits |
| // type like this: |
| // |
| // struct ValidTraits { |
| // ValidTraits(MyTrait); |
| // ... |
| // }; |
| // |
| // You can 'inherit' valid traits like so: |
| // |
| // struct MoreValidTraits { |
| // MoreValidTraits(ValidTraits); // Pull in traits from ValidTraits. |
| // MoreValidTraits(MyOtherTrait); |
| // ... |
| // }; |
| template <class ValidTraits, class... ArgTypes> |
| using AreValidTraits = |
| std::bool_constant<all_of({IsValidTrait<ValidTraits, ArgTypes>::value...})>; |
| |
| // Helper to make getting an enum from a trait more readable. |
| template <typename Enum, typename... Args> |
| static constexpr Enum GetEnum(Args... args) { |
| return GetTraitFromArgList<RequiredEnumTraitFilter<Enum>>(args...); |
| } |
| |
| // Helper to make getting an enum from a trait with a default more readable. |
| template <typename Enum, Enum DefaultValue, typename... Args> |
| static constexpr Enum GetEnum(Args... args) { |
| return GetTraitFromArgList<EnumTraitFilter<Enum, DefaultValue>>(args...); |
| } |
| |
| // Helper to make getting an optional enum from a trait with a default more |
| // readable. |
| template <typename Enum, typename... Args> |
| static constexpr absl::optional<Enum> GetOptionalEnum(Args... args) { |
| return GetTraitFromArgList<OptionalEnumTraitFilter<Enum>>(args...); |
| } |
| |
| // Helper to make checking for the presence of a trait more readable. |
| template <typename Trait, typename... Args> |
| struct HasTrait : ParameterPack<Args...>::template HasType<Trait> { |
| static_assert( |
| count({std::is_constructible<Trait, Args>::value...}, true) <= 1, |
| "The traits bag contains multiple traits of the same type."); |
| }; |
| |
| // If you need a template vararg constructor to delegate to a private |
| // constructor, you may need to add this to the private constructor to ensure |
| // it's not matched by accident. |
| struct NotATraitTag {}; |
| |
| } // namespace trait_helpers |
| } // namespace base |
| |
| #endif // BASE_TRAITS_BAG_H_ |