Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect if a enum is defined

Let's say I have the following code:

// exposition-only, I can't use this macro in my solution
#if defined(HAS_MY_ENUM)
enum my_enum {
    zero,
    one,
};
#endif

enum empty {
    placeholder, // an enum must always have a value
};

I'd love to have a conditional type alias my_enum_or_empty that is set to my_enum if it is defined, otherwise it's set to empty, e.g.:

using my_enum_or_empty = std::conditional<my_enum_exists, my_enum, empty>;

I want a SFINAE-based solution that gives me my_enum_exists.

I was thinking of this approach, but it requires a forward-declaration of my_enum, and it cannot be forward declared, because it is sizeless. And I cannot add a size, because this enum comes from an external library. Do you have any ideas?

Restrictions

My use case is that this my_enum is defined in an external C library that I don't have control over. I need to compile my code against two versions of this library (with my_enum and without). The header sources, where my_enum is defined cannot be changed i.e. it must be an old C-style enum.

The library version is not known at compile time. Sadly, there is no LIBRARY_VERSION macro. I need to rely on this enum alone.

I cannot rely on the build system. In fact, I am doing a C++ header-only wrapper around this C code, hence there is no build system. Neither can I move responsibility to my customer. There are more than one type missing and there are many versions of this library, i.e. it'd be too complex for my wrapper user.

like image 545
ivaigult Avatar asked Sep 06 '25 00:09

ivaigult


1 Answers

@Artyer suggests a more practical approach in his answer. I'm leaving this here mostly as a technical curiosity.


What you can do, if you know the name of the one of the constants in the enum, is to define it as a macro (assuming the target header doesn't use that name anywhere else), which would expand to itself plus some magic to detect whether it was used or not (stateful metaprogramming).

The following works with the assumption that the constant doesn't have a = value specified. If it does and you know the exact value beforehand, supporting that case should be trivial. If it does but you don't know the value, it might still be possible, but will be somewhat more ugly.

#include <type_traits>

namespace EnumDetector
{
    namespace detail
    {
        constexpr void _adl_EnumMarker() {} // Dummy ADL target.

        template <typename T>
        struct Reader
        {
            #if defined(__GNUC__) && !defined(__clang__)
            #pragma GCC diagnostic push
            #pragma GCC diagnostic ignored "-Wnon-template-friend"
            #endif
            friend constexpr auto _adl_EnumMarker(Reader<T>);
            #if defined(__GNUC__) && !defined(__clang__)
            #pragma GCC diagnostic pop
            #endif
        };

        template <typename T>
        struct Writer
        {
            friend constexpr auto _adl_EnumMarker(Reader<T>) {return true;}
        };

        template <typename T, typename = void> struct Value : std::false_type {};
        template <typename T> struct Value<T, std::enable_if_t<_adl_EnumMarker(Reader<T>{})>> : std::true_type {};
    }

    template <typename T>
    inline constexpr bool HaveEnum = detail::Value<T>::value;
}

#define ENUM_DETECTOR_REGISTER_WITHOUT_VALUE(tag_, enumerator_) \
    DETAIL_ENUM_DETECTOR_REGISTER_WITHOUT_VALUE(__COUNTER__, tag_, enumerator_)

#define DETAIL_ENUM_DETECTOR_REGISTER_WITHOUT_VALUE(counter_, tag_, enumerator_) \
    DETAIL_ENUM_DETECTOR_CAT(_enum_detector_helper_,counter_), \
    DETAIL_ENUM_DETECTOR_CAT(_enum_detector_helper2_,counter_) = (void(::EnumDetector::detail::Writer<tag_>{}), 0), \
    enumerator_ = DETAIL_ENUM_DETECTOR_CAT(_enum_detector_helper_,counter_)

#define DETAIL_ENUM_DETECTOR_CAT(x, y) DETAIL_ENUM_DETECTOR_CAT_(x, y)
#define DETAIL_ENUM_DETECTOR_CAT_(x, y) x##y

// ---
// Repeat following for all enums:

struct MyEnumTag {};

#define my_enum_constant ENUM_DETECTOR_REGISTER_WITHOUT_VALUE(MyEnumTag, my_enum_constant)

// ---

#ifdef YOUR_LIB_INCLUDE_GUARD // <-- Customize this for the target library, or remove altogether if it uses `#pragma once`.
#error "Must not include this stuff elsewhere.."
#endif

// #include <stuff.h>
enum SomeEnum { x, y, my_enum_constant, z, w }; // Try commenting me out to trigger the assertion.

// ---
// Repeat following for all enums:

#undef my_enum_constant

// ---
// Lastly, the usage:
static_assert(EnumDetector::HaveEnum<MyEnumTag>);
like image 191
HolyBlackCat Avatar answered Sep 07 '25 13:09

HolyBlackCat