// 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 <optional> #include <tuple> #include <type_traits> #include <utility> #include "base/parameter_pack.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> // requires trait_helpers::AreValidTraits<ValidTraits, ArgTypes...> // 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 { … }; // 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> requires std::constructible_from<TraitFilterType, ArgType> constexpr TraitFilterType GetTraitFromArg(CallFirstTag, ArgType arg) { … } template <class TraitFilterType, class ArgType> constexpr InvalidTrait GetTraitFromArg(CallSecondTag, ArgType arg) { … } // 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> requires(std::constructible_from<TraitFilterType, ArgTypes> || ...) constexpr TraitFilterType GetTraitFromArgListImpl(CallFirstTag, ArgTypes... args) { … } template <class TraitFilterType, class... ArgTypes> constexpr TraitFilterType GetTraitFromArgListImpl(CallSecondTag, ArgTypes... args) { … } // 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) { … } // Helper class to implemnent a |TraitFilterType|. template <typename T, typename _ValueType = T> struct BasicTraitFilter { … }; template <typename ArgType, ArgType DefaultValue> struct EnumTraitFilter : public BasicTraitFilter<ArgType> { … }; template <typename ArgType> struct OptionalEnumTraitFilter : public BasicTraitFilter<ArgType, std::optional<ArgType>> { … }; // 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> { … }; // Note EmptyTrait is always regarded as valid to support filtering. IsValidTrait; // 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); // ... // }; AreValidTraits; // Helper to make getting an enum from a trait more readable. template <typename Enum, typename... Args> static constexpr Enum GetEnum(Args... 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) { … } // Helper to make getting an optional enum from a trait with a default more // readable. template <typename Enum, typename... Args> static constexpr std::optional<Enum> GetOptionalEnum(Args... 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> { … }; // 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_