iva*_*ult 3 c++ enums metaprogramming sfinae
假设我有以下代码:
// 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
};
Run Code Online (Sandbox Code Playgroud)
我希望有一个条件类型别名my_enum_or_empty,如果已定义,则设置为my_enum,否则设置为empty,例如:
using my_enum_or_empty = std::conditional<my_enum_exists, my_enum, empty>;
Run Code Online (Sandbox Code Playgroud)
我想要一个基于 SFINAE 的解决方案,它可以为我提供my_enum_exists.
我正在考虑这种方法,但它需要 的前向声明my_enum,并且不能前向声明,因为它是无大小的。我无法添加尺寸,因为它enum来自外部库。你有什么想法?
我的用例是,这my_enum是在我无法控制的外部 C 库中定义的。我需要针对该库的两个版本(带my_enum和不带)编译我的代码。定义的标头源my_enum不能更改,即它必须是旧的 C 样式enum。
编译时不知道库版本。可惜没有LIBRARY_VERSION宏。我只需要依靠这一点enum。
我不能依赖构建系统。事实上,我正在围绕这个 C 代码做一个 C++头文件包装器,因此没有构建系统。我也不能将责任转嫁给我的客户。缺少不止一种类型,并且该库有许多版本,即对于我的包装器用户来说它太复杂了。
@Artyer在他的回答中提出了一种更实用的方法。我将其留在这里主要是出于技术上的好奇心。
如果您知道枚举中常量之一的名称,您可以做的就是将其定义为宏(假设目标标头在其他任何地方都没有使用该名称),该宏将扩展为自身加上一些魔法检测它是否被使用(有状态元编程)。
以下内容假设常量没有指定= value。如果确实如此,并且您事先知道确切的值,那么支持这种情况应该是微不足道的。如果确实如此,但您不知道其价值,它仍然可能,但会更难看。
#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>);
Run Code Online (Sandbox Code Playgroud)
像这样的事情怎么样:
template<typename T = int>
constexpr bool my_enum_exists = requires { my_enum(T{}); };
Run Code Online (Sandbox Code Playgroud)
(无需任何要求即可实现,只需在表达式上添加 SFINAE 即可my_enum(T{}))。
当my_enum 被定义为类型时,这只会检查您是否可以执行功能强制转换my_enum(0)(您可以执行此操作),因此它会通过。
当它没有定义时,这将检查你是否可以调用一个名为my_enumwith的函数T{}(通过 ADL,所以你不需要一个名为 的虚拟函数my_enum),这对于非类类型总是会失败int。
利用这种“adl 或类型转换”,您可以创建一个结构体,如果找不到枚举名称,则可以通过 ADL 返回空类型来查找函数。这可以很容易地扩展到多种类型:
enum empty {
placeholder, // an enum must always have a value
};
struct enum_existance_checker {
// Convert to enum type or int argument type
template<typename T> 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<my_enum_or_empty, my_enum>);
// 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<my_other_enum_or_empty, empty>);
Run Code Online (Sandbox Code Playgroud)