7

I searched a bit here on SO and was surprise that I didn't find any similar question. Happy for any hints in case this has already been answered.

I have a codebase with a lot of enum classes defined. Some of them specify a totalNum constant like

enum class Foo : int
{
    a,
    b,
    c,

    totalNum
}

Others don't have this like

enum class Bar : bool
{
    oneOption,
    otherOption
}

Then I have a function basically like this

template <class EnumClassType>
EnumClassType typeToEnum (typename std::underlying_type<EnumClassType>::type value)
{
    // If you hit this assertion, the value is outside of the valid enum range
    assert (isPositiveAndBelow (value, decltype (value) (EnumClassType::totalNum)));

    return EnumClassType (value);
}

While this works and makes sense for the an enum with totalNum specified, I'd like to skip this assert in case there is no such identifier in the enum. Is there any way to do that? The codebase is currently using C++ 14 but C++ 17 solutions are also welcome due to an upcoming compiler change.

1
  • You can use SFINAE to provide an overload where if an item is an enum and has ::totalNum it then does the static assert. Commented Jul 21, 2021 at 16:10

3 Answers 3

6

Found the answer myself in the meantime, using an approach like @jfh mentioned in the comments.

First of all this is a way to check if an enum class contains an identifier with a certain name

template <class EnumToTest>
class EnumConstantDefined_totalNum
{
private:
    using Yes = int8_t;
    using No = int16_t;

    template <class E>
    static Yes test (decltype (E::totalNum)*);

    template <class E>
    static No test (...);

public:
    static constexpr bool value = sizeof (test<EnumToTest> (0)) == sizeof (Yes);
};

Then we can employ SFINAE to specify two overloads for both kinds of enums.

template <class EnumType>
std::enable_if_t<EnumConstantDefined_totalNum<EnumType>::value, void> assertValueIsInRange (typename std::underlying_type<EnumType>::type value)
{
    assert (isPositiveAndBelow (value, decltype (value) (EnumType::totalNum)));
}

template <class EnumType>
std::enable_if_t<! EnumConstantDefined_totalNum<EnumType>::value, void> assertValueIsInRange (typename std::underlying_type<EnumType>::type)
{
    // do nothing
}

Then use this assertion function in the actual conversion function

/**
   Casts a value matching an enum class underlying type to an enum class constant and asserts that the
   value is inside the valid enum range
 */
template <class EnumClassType>
EnumClassType typeToEnum (typename std::underlying_type<EnumClassType>::type value)
{
    assertValueIsInRange<EnumClassType> (value);

    return EnumClassType (value);
}
Sign up to request clarification or add additional context in comments.

1 Comment

Beat me by 15s. ;-)
0

Is too late to play?

I propose a slightly different solution that uses tag dispatching and components already defined in the standard (std::true_type and std::false_type) that simplify the development.

Instead your class EnumConstantDefined_totalNum you can simply declare a couple of template function and a template constexpr variable as follows

template <typename Enum>
auto checkEnumFunc (int) -> decltype( Enum::totalNum, std::true_type{});

template <typename>
auto checkEnumFunc (long) -> std::false_type;

template <typename Enum>
static constexpr auto checkEnum = decltype(checkEnumFunc<Enum>(0))::value;

The assertValueIsInRange() template functions become

template <class EnumType>
std::enable_if_t<checkEnum<EnumType>, void> assertValueIsInRange (std::underlying_type_t<EnumType> value)
{
  assert (value <  static_cast<decltype(value)>(EnumType::totalNum));
}

template <class EnumType>
std::enable_if_t<not checkEnum<EnumType>, void> assertValueIsInRange (std::underlying_type_t<EnumType> value)
{
    // do nothing
}

The following is a full compiling C++14 example


#include <cassert>
#include <cstdint>
#include <type_traits>

enum class Foo : int
{
    a,
    b,
    c,

    totalNum
};

enum class Bar : bool
{
    oneOption,
    otherOption
};


template <typename Enum>
auto checkEnumFunc (int) -> decltype( Enum::totalNum, std::true_type{});

template <typename>
auto checkEnumFunc (long) -> std::false_type;

template <typename Enum>
static constexpr auto checkEnum = decltype(checkEnumFunc<Enum>(0))::value;

template <class EnumType>
std::enable_if_t<checkEnum<EnumType>, void> assertValueIsInRange (std::underlying_type_t<EnumType> value)
{
  assert (value <  static_cast<decltype(value)>(EnumType::totalNum));
}

template <class EnumType>
std::enable_if_t<not checkEnum<EnumType>, void> assertValueIsInRange (std::underlying_type_t<EnumType> value)
{
    // do nothing
}

template <class EnumClassType>
EnumClassType typeToEnum (typename std::underlying_type<EnumClassType>::type value)
{
    assertValueIsInRange<EnumClassType> (value);

    return EnumClassType (value);
}

int main ()
{
  typeToEnum<Foo>(0);  // doesn't fails
  // typeToEnum<Foo>(10); // fails!
  typeToEnum<Bar>(0);  // doesn't fails
  typeToEnum<Bar>(10); // doesn't fails
}

Comments

0

I find enable_if-style SFINAE hard to read (and write). When you're able to move past C++14 and C++17, you can simplify with constraints.

#include <cassert>
#include <type_traits>

template <typename EnumT>
bool constexpr enum_has_total() { return false; }

template <typename EnumT>
bool constexpr enum_has_total()
    requires (EnumT::total == EnumT::total)
{ return true; }

template <typename EnumT>
EnumT constexpr get_enum_total()
    requires (enum_has_total<EnumT>())
{
    return EnumT::total;
}

template <typename EnumT, typename ValueT>
EnumT constexpr enum_from_value(ValueT value)
    requires std::is_integral_v<ValueT> &&
             std::is_convertible_v<std::underlying_type_t<EnumT>, ValueT>
{
    if constexpr (enum_has_total<EnumT>()) {
        assert(0 <= value);
        assert(value <= static_cast<ValueT>(get_enum_total<EnumT>()));
    }
    return EnumT{value};
}

enum class E1 { a, b, c, total };
enum class E2 { p, Q, r, s, t };
enum class E3 { x, y, Q, z, total };

int main() {
    E1 constexpr x1   = enum_from_value<E1>(1);
    E2 constexpr x2_1 = enum_from_value<E2>(3);
    E2 constexpr x2_2 = enum_from_value<E2>(47); // undetected error
    E3           x3_1 = enum_from_value<E3>(47); // run-time assertion failure
    E3 constexpr x3_2 = enum_from_value<E3>(47); // compile-time error
    return 0;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.