/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* Poor man's reflection for enumerations. */ #ifndef mozilla_DefineEnum_h #define mozilla_DefineEnum_h #include // for size_t #include // for std::ostream #include "mozilla/MacroArgs.h" // for MOZ_ARG_COUNT #include "mozilla/MacroForEach.h" // for MOZ_FOR_EACH /** * MOZ_UNWRAP_ARGS is a helper macro that unwraps a list of comma-separated * items enclosed in parentheses, to yield just the items. * * Usage: |MOZ_UNWRAP_ARGS foo| (note the absence of parentheses in the * invocation), where |foo| is a parenthesis-enclosed list. * For exampe if |foo| is |(3, 4, 5)|, then the expansion is just |3, 4, 5|. */ #define MOZ_UNWRAP_ARGS(...) __VA_ARGS__ /** * MOZ_DEFINE_ENUM(aEnumName, aEnumerators) is a macro that allows * simultaneously defining an enumeration named |aEnumName|, and a constant * that stores the number of enumerators it has. * * The motivation is to allow the enumeration to evolve over time without * either having to manually keep such a constant up to date, or having to * add a special "sentinel" enumerator for this purpose. (While adding a * "sentinel" enumerator is trivial, it causes headaches with "switch" * statements. We often try to write "switch" statements whose cases exhaust * the enumerators and don't have a "default" case, so that if a new * enumerator is added and we forget to handle it in the "switch", the * compiler points it out. But this means we need to explicitly handle the * sentinel in every "switch".) * * |aEnumerators| is expected to be a comma-separated list of enumerators, * enclosed in parentheses. The enumerators may NOT have associated * initializers (an attempt to have one will result in a compiler error). * This ensures that the enumerator values are in the range [0, N), where N * is the number of enumerators. * * The list of enumerators cannot contain a trailing comma. This is a * limitation of MOZ_FOR_EACH, which we use in the implementation; if * MOZ_FOR_EACH supported trailing commas, we could too. * * The generated constant has the name "k" + |aEnumName| + "Count", and type * "size_t". The enumeration and the constant are both defined in the scope * in which the macro is invoked. * * For convenience, a constant of the enumeration type named * "kHighest" + |aEnumName| is also defined, whose value is the highest * valid enumerator, assuming the enumerators have contiguous values starting * from 0. * * Invocation of the macro may be followed by a semicolon, if one prefers a * more declaration-like syntax. * * Example invocation: * MOZ_DEFINE_ENUM(MyEnum, (Foo, Bar, Baz)); * * This expands to: * enum MyEnum { Foo, Bar, Baz }; * constexpr size_t kMyEnumCount = 3; * constexpr MyEnum kHighestMyEnum = MyEnum(kMyEnumCount - 1); * // some static_asserts to ensure the values are in the range [0, 3) * * The macro also has several variants: * * - A |_CLASS| variant, which generates an |enum class| instead of * a plain enum. * * - A |_WITH_BASE| variant which generates an enum with a specified * underlying ("base") type, which is provided as an additional * argument in second position. * * - An |_AT_CLASS_SCOPE| variant, designed for enumerations defined * at class scope. For these, the generated constants are static, * and have names prefixed with "s" instead of "k" as per * naming convention. * * - A |_TOSTRING| variant, which generates an EnumValueToString function, * converting the enum items to strings, and implements an "operator<<", * providing a consistent way to convert enums to strings by * mozilla::ToString function regardless of their definition context. * For users needing C-string compatibility for logging in restricted * contexts or performance sensitive applications, EnumValueToString is * preferred. * * (and combinations of these). */ /* * A helper macro for asserting that an enumerator does not have an initializer. * * The static_assert and the comparison are just scaffolding; the important * part is forming the expression |aEnumName::aEnumeratorDecl|. * * If |aEnumeratorDecl| is just the enumerator name without an identifier, * this expression compiles fine. However, if |aEnumeratorDecl| includes an * initializer, as in |eEnumerator = initializer|, then this will fail to * compile in expression context, since |eEnumerator| is not an lvalue. * * (The static_assert itself should always pass in the absence of the above * error, since turning on a bit can only increase an integer value. It just * provides a place to put the expression we want to form.) */ #define MOZ_ASSERT_ENUMERATOR_HAS_NO_INITIALIZER(aEnumName, aEnumeratorDecl) \ static_assert( \ int(aEnumName::aEnumeratorDecl) <= \ (int(aEnumName::aEnumeratorDecl) | 1), \ "MOZ_DEFINE_ENUM does not allow enumerators to have initializers"); #define MOZ_DEFINE_ENUM_IMPL(aEnumName, aClassSpec, aBaseSpec, aEnumerators) \ enum aClassSpec aEnumName aBaseSpec{MOZ_UNWRAP_ARGS aEnumerators}; \ constexpr size_t k##aEnumName##Count = MOZ_ARG_COUNT aEnumerators; \ constexpr aEnumName kHighest##aEnumName = \ aEnumName(k##aEnumName##Count - 1); \ MOZ_FOR_EACH(MOZ_ASSERT_ENUMERATOR_HAS_NO_INITIALIZER, (aEnumName, ), \ aEnumerators) #define MOZ_DEFINE_ENUM(aEnumName, aEnumerators) \ MOZ_DEFINE_ENUM_IMPL(aEnumName, , , aEnumerators) #define MOZ_DEFINE_ENUM_WITH_BASE(aEnumName, aBaseName, aEnumerators) \ MOZ_DEFINE_ENUM_IMPL(aEnumName, , : aBaseName, aEnumerators) #define MOZ_DEFINE_ENUM_CLASS(aEnumName, aEnumerators) \ MOZ_DEFINE_ENUM_IMPL(aEnumName, class, , aEnumerators) #define MOZ_DEFINE_ENUM_CLASS_WITH_BASE(aEnumName, aBaseName, aEnumerators) \ MOZ_DEFINE_ENUM_IMPL(aEnumName, class, : aBaseName, aEnumerators) #define MOZ_DEFINE_ENUM_AT_CLASS_SCOPE_IMPL(aEnumName, aClassSpec, aBaseSpec, \ aEnumerators) \ enum aClassSpec aEnumName aBaseSpec{MOZ_UNWRAP_ARGS aEnumerators}; \ constexpr static size_t s##aEnumName##Count = MOZ_ARG_COUNT aEnumerators; \ constexpr static aEnumName sHighest##aEnumName = \ aEnumName(s##aEnumName##Count - 1); \ MOZ_FOR_EACH(MOZ_ASSERT_ENUMERATOR_HAS_NO_INITIALIZER, (aEnumName, ), \ aEnumerators) #define MOZ_DEFINE_ENUM_AT_CLASS_SCOPE(aEnumName, aEnumerators) \ MOZ_DEFINE_ENUM_AT_CLASS_SCOPE_IMPL(aEnumName, , , aEnumerators) #define MOZ_DEFINE_ENUM_WITH_BASE_AT_CLASS_SCOPE(aEnumName, aBaseName, \ aEnumerators) \ MOZ_DEFINE_ENUM_AT_CLASS_SCOPE_IMPL(aEnumName, , : aBaseName, aEnumerators) #define MOZ_DEFINE_ENUM_CLASS_AT_CLASS_SCOPE(aEnumName, aEnumerators) \ MOZ_DEFINE_ENUM_AT_CLASS_SCOPE_IMPL(aEnumName, class, , aEnumerators) #define MOZ_DEFINE_ENUM_CLASS_WITH_BASE_AT_CLASS_SCOPE(aEnumName, aBaseName, \ aEnumerators) \ MOZ_DEFINE_ENUM_AT_CLASS_SCOPE_IMPL(aEnumName, class, \ : aBaseName, aEnumerators) #define MOZ_DEFINE_ENUM_TO_ENUM_TEXT(aEnumeratorDecl) #aEnumeratorDecl #define MOZ_DEFINE_ENUM_TOSTRING_FUNC_IMPL(aEnumName, aEnumerators, aFriend) \ inline static const char* EnumValueToString(const aEnumName& aEnum) { \ static constexpr const char* kMappedStrings[] = {MOZ_FOR_EACH_SEPARATED( \ MOZ_DEFINE_ENUM_TO_ENUM_TEXT, (, ), (), aEnumerators)}; \ return kMappedStrings[static_cast(aEnum)]; \ } \ aFriend inline std::ostream& operator<<(std::ostream& aStream, \ const aEnumName& aEnum) { \ aStream << EnumValueToString(aEnum); \ return aStream; \ } #define MOZ_DEFINE_ENUM_TOSTRING_FUNC(aEnumName, aEnumerators) \ MOZ_DEFINE_ENUM_TOSTRING_FUNC_IMPL(aEnumName, aEnumerators, ) #define MOZ_DEFINE_ENUM_TOSTRING_FUNC_IN_CLASS(aEnumName, aEnumerators) \ MOZ_DEFINE_ENUM_TOSTRING_FUNC_IMPL(aEnumName, aEnumerators, friend) #define MOZ_DEFINE_ENUM_WITH_BASE_AND_TOSTRING(aEnumName, aBaseName, \ aEnumerators) \ MOZ_DEFINE_ENUM_WITH_BASE(aEnumName, aBaseName, aEnumerators) \ MOZ_DEFINE_ENUM_TOSTRING_FUNC(aEnumName, aEnumerators) #define MOZ_DEFINE_ENUM_CLASS_WITH_TOSTRING(aEnumName, aEnumerators) \ MOZ_DEFINE_ENUM_CLASS(aEnumName, aEnumerators) \ MOZ_DEFINE_ENUM_TOSTRING_FUNC(aEnumName, aEnumerators) #define MOZ_DEFINE_ENUM_CLASS_WITH_BASE_AND_TOSTRING(aEnumName, aBaseName, \ aEnumerators) \ MOZ_DEFINE_ENUM_CLASS_WITH_BASE(aEnumName, aBaseName, aEnumerators) \ MOZ_DEFINE_ENUM_TOSTRING_FUNC(aEnumName, aEnumerators) #define MOZ_DEFINE_ENUM_WITH_TOSTRING_AT_CLASS_SCOPE(aEnumName, aEnumerators) \ MOZ_DEFINE_ENUM_AT_CLASS_SCOPE(aEnumName, aEnumerators) \ MOZ_DEFINE_ENUM_TOSTRING_FUNC_IN_CLASS(aEnumName, aEnumerators) #define MOZ_DEFINE_ENUM_WITH_BASE_AND_TOSTRING_AT_CLASS_SCOPE( \ aEnumName, aBaseName, aEnumerators) \ MOZ_DEFINE_ENUM_WITH_BASE_AT_CLASS_SCOPE(aEnumName, aBaseName, aEnumerators) \ MOZ_DEFINE_ENUM_TOSTRING_FUNC_IN_CLASS(aEnumName, aEnumerators) #define MOZ_DEFINE_ENUM_CLASS_WITH_TOSTRING_AT_CLASS_SCOPE(aEnumName, \ aEnumerators) \ MOZ_DEFINE_ENUM_CLASS_AT_CLASS_SCOPE(aEnumName, aEnumerators) \ MOZ_DEFINE_ENUM_TOSTRING_FUNC_IN_CLASS(aEnumName, aEnumerators) #define MOZ_DEFINE_ENUM_CLASS_WITH_BASE_AND_TOSTRING_AT_CLASS_SCOPE( \ aEnumName, aBaseName, aEnumerators) \ MOZ_DEFINE_ENUM_CLASS_WITH_BASE_AT_CLASS_SCOPE(aEnumName, aBaseName, \ aEnumerators) \ MOZ_DEFINE_ENUM_TOSTRING_FUNC_IN_CLASS(aEnumName, aEnumerators) #endif // mozilla_DefineEnum_h