// Copyright 2018 The Chromium Authors. All rights reserved. // 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 #include #include #include #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 ::value>> // constexpr void DoSomethingAwesome(ArgTypes... args) // : enable_feature_x( // trait_helpers::HasTrait()), // color(trait_helpers::GetEnum(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 // void MyFunc(Args... args) { // DoSomethingWithTraits( // base::trait_helpers::Exclude::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 struct Exclude { template ::template HasType::value>* = nullptr> static constexpr EmptyTrait Filter(T t) { return EmptyTrait(); } template ::template HasType::value>* = nullptr> static constexpr T Filter(T t) { 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 ::value>> constexpr TraitFilterType GetTraitFromArg(CallFirstTag, ArgType arg) { return TraitFilterType(arg); } template 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 ::value...})>> constexpr TraitFilterType GetTraitFromArgListImpl(CallFirstTag, ArgTypes... args) { return std::get(std::make_tuple( GetTraitFromArg(CallFirstTag(), args)...)); } template constexpr TraitFilterType GetTraitFromArgListImpl(CallSecondTag, ArgTypes... args) { static_assert(std::is_constructible::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 constexpr typename TraitFilterType::ValueType GetTraitFromArgList( ArgTypes... args) { static_assert( count({std::is_constructible::value...}, true) <= 1, "The traits bag contains multiple traits of the same type."); return GetTraitFromArgListImpl(CallFirstTag(), args...); } // Helper class to implemnent a |TraitFilterType|. template struct BasicTraitFilter { using ValueType = _ValueType; constexpr BasicTraitFilter(ValueType v) : value(v) {} constexpr operator ValueType() const { return value; } ValueType value = {}; }; template struct EnumTraitFilter : public BasicTraitFilter { constexpr EnumTraitFilter() : BasicTraitFilter(DefaultValue) {} constexpr EnumTraitFilter(ArgType arg) : BasicTraitFilter(arg) {} }; template struct OptionalEnumTraitFilter : public BasicTraitFilter> { constexpr OptionalEnumTraitFilter() : BasicTraitFilter>(absl::nullopt) {} constexpr OptionalEnumTraitFilter(ArgType arg) : BasicTraitFilter>(arg) {} }; // Tests whether multiple given argtument types are all valid traits according // to the provided ValidTraits. To use, define a ValidTraits template struct RequiredEnumTraitFilter : public BasicTraitFilter { constexpr RequiredEnumTraitFilter(ArgType arg) : BasicTraitFilter(arg) {} }; // Note EmptyTrait is always regarded as valid to support filtering. template using IsValidTrait = std::disjunction, std::is_same>; // 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 using AreValidTraits = std::bool_constant::value...})>; // Helper to make getting an enum from a trait more readable. template static constexpr Enum GetEnum(Args... args) { return GetTraitFromArgList>(args...); } // Helper to make getting an enum from a trait with a default more readable. template static constexpr Enum GetEnum(Args... args) { return GetTraitFromArgList>(args...); } // Helper to make getting an optional enum from a trait with a default more // readable. template static constexpr absl::optional GetOptionalEnum(Args... args) { return GetTraitFromArgList>(args...); } // Helper to make checking for the presence of a trait more readable. template struct HasTrait : ParameterPack::template HasType { static_assert( count({std::is_constructible::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_