如何检测枚举是否已定义

huangapple go评论76阅读模式
英文:

How to detect if a enum is defined

问题

// 让我们假设我有以下的代码:
```c++
// 仅供展示,我不能在我的解决方案中使用这个宏
#if defined(HAS_MY_ENUM)
enum my_enum {
    zero,
    one,
};
#endif

enum empty {
    placeholder, // 枚举必须始终有一个值
};

我想要有一个条件类型别名 my_enum_or_empty,如果已定义 my_enum,则设置为 my_enum,否则设置为 empty,例如:

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

我需要一个基于 SFINAE 的解决方案,以获取 my_enum_exists

我考虑过这种方法,但它需要对 my_enum 进行前向声明,而不能前向声明它,因为它是无大小的。我也不能添加大小,因为这个 enum 来自外部库。您有任何想法吗?

限制

我的用例是,这个 my_enum 在一个我无法控制的外部 C 库中定义。我需要编译我的代码以适应这个库的两个版本(一个有 my_enum,一个没有)。my_enum 的定义所在的头文件不能被更改,即它必须是一个旧的 C 风格 enum

在编译时未知库的版本。遗憾的是,没有 LIBRARY_VERSION 宏。我必须仅依赖这个 enum

我不能依赖于构建系统。事实上,我正在做一个 C++ 仅头文件 的包装器,用于包装这个 C 代码,因此没有构建系统。我也不能将责任转移给我的客户。有多个缺少的类型,而且这个库有许多版本,即对我的包装器用户来说,这将变得太复杂。


<details>
<summary>英文:</summary>

Let&#39;s say I have the following code:
```c++
// exposition-only, I can&#39;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&lt;my_enum_exists, my_enum, empty&gt;;

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.

答案1

得分: 8

以下是翻译的内容:

@Artyer在他的回答中提出了一种更实际的方法(链接在此处)。我将这里留下,主要是作为技术上的一种好奇心。

如果你知道枚举中其中一个常量的名称,可以将其定义为宏(假设目标头文件没有在任何其他地方使用该名称),该宏会扩展为它本身以及一些魔法代码来检测它是否被使用过(有状态的元编程)。

以下代码假设该常量没有指定= value。如果它有,并且你事先知道确切的值,支持那种情况应该是微不足道的。如果它有,但你不知道该值,这仍然可能是可能的,但会显得更加复杂。

英文:

@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 &lt;type_traits&gt;

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

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

        template &lt;typename T&gt;
        struct Writer
        {
            friend constexpr auto _adl_EnumMarker(Reader&lt;T&gt;) {return true;}
        };

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

    template &lt;typename T&gt;
    inline constexpr bool HaveEnum = detail::Value&lt;T&gt;::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&lt;tag_&gt;{}), 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 // &lt;-- Customize this for the target library, or remove altogether if it uses `#pragma once`.
#error &quot;Must not include this stuff elsewhere..&quot;
#endif

// #include &lt;stuff.h&gt;
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&lt;MyEnumTag&gt;);

答案2

得分: 6

Here's the translated content:

这个怎么样:

```c++
template&lt;typename T = int&gt;
constexpr bool my_enum_exists = requires { my_enum(T{}); };

(可以不使用 requires 来实现,只需在表达式 my_enum(T{}) 上使用 SFINAE)。

my_enum 被定义为类型时,这只会检查是否可以执行函数转换 my_enum(0),因此它会通过。

当它没有被定义时,这将检查是否可以调用名为 my_enum 的函数,参数为 T{}(通过ADL,因此不需要一个名为 my_enum 的虚拟函数),对于非类类型 int,这将始终失败。


利用这个“ADL或类型转换”的特性,您可以创建一个结构体,通过ADL查找函数,如果找不到枚举名称,则返回空类型。这可以轻松扩展到多个类型:

enum empty {
    placeholder, // 枚举必须始终有一个值
};
struct enum_existance_checker {
    // 转换为枚举类型或 int 参数类型
    template&lt;typename T&gt; operator T();

    friend empty my_enum(int);
    friend empty my_other_enum(int);
    friend empty my_third_enum(int);
};

enum my_enum {};
// enum my_other_enum {};
enum my_third_enum {};

// 这是对 my_enum 的转换,使用转换操作符。
using my_enum_or_empty = decltype(my_enum(enum_existance_checker{}));
static_assert(std::is_same_v&lt;my_enum_or_empty, my_enum&gt;);

// 这是对 friend 函数的调用,转换为 int
using my_other_enum_or_empty = decltype(my_other_enum(enum_existance_checker{}));
static_assert(std::is_same_v&lt;my_other_enum_or_empty, empty&gt;);

<details>
<summary>英文:</summary>

How about something like this:

```c++
template&lt;typename T = int&gt;
constexpr bool my_enum_exists = requires { my_enum(T{}); };

(Which can be implemented without the requires, you just need to SFINAE on the expression my_enum(T{})).

When my_enum is defined as a type, this will just check if you can do the functional cast my_enum(0), which you can, so it passes.

When it's not defined, this will check if you can call a function called my_enum with T{} (via ADL, so you don't need a dummy function called my_enum), which will always fail for non-class type int.


Taking advantage of this "adl or type cast", you can make a struct that can find functions by ADL returning your empty type if the enum name can't be found. This can easily be expanded for multiple types:

enum empty {
    placeholder, // an enum must always have a value
};
struct enum_existance_checker {
    // Convert to enum type or int argument type
    template&lt;typename T&gt; operator T();

    friend empty my_enum(int);
    friend empty my_other_enum(int);
    friend empty my_third_enum(int);
};

enum my_enum {};
// enum my_other_enum {};
enum my_third_enum {};

// This is a cast to my_enum, using the conversion operator.
using my_enum_or_empty = decltype(my_enum(enum_existance_checker{}));
static_assert(std::is_same_v&lt;my_enum_or_empty, my_enum&gt;);

// This is a call to the friend function, converting to int
using my_other_enum_or_empty = decltype(my_other_enum(enum_existance_checker{}));
static_assert(std::is_same_v&lt;my_other_enum_or_empty, empty&gt;);

huangapple
  • 本文由 发表于 2023年6月13日 01:09:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/76458888.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定