Реализация is_enum
Каким способом можно реализовать std::is_enum? Я смотрел реализации в libstdc++ и libcxx, но и там и там используются интринсики компилятора. Как можно реализовать эту функцию без их использования?
Ответы (2 шт):
Благодаря тому, что в C++20 лямбдам было разрешено появляться в параметрах шаблона, это можно реализовать следующим образом:
#include <type_traits>
namespace details {
enum TestEnum { }; // Этот класс во избежание фальсификаций необходимо спрятать от пользователя
}
template<typename T, auto f = []() {
switch(std::declval<T>()) { }
}>
auto test_enum(int) ->
decltype(void(static_cast<details::TestEnum>(std::declval<T>())),
std::true_type{});
template<typename T> auto test_enum(...) -> std::false_type;
template<typename T>
struct is_enum: std::bool_constant<decltype(test_enum<T>(0))::value && !std::is_arithmetic_v<T>> { };
Это работает за счет того, что, согласно стандарту, в качестве условия switch выражения допустим любой арифметический тип или тип-перечисление, а также тому, что любой тип-перечисление может быть явно приведен к любому другому типу-перечислению.
Таким образом, возвращаемое значение функции test_enum будет иметь тип std::true_type, если переданный в качестве параметра шаблона тип является или перечислением, или числовым типом, и std::false_type, если вышеобозначенное условие не соблюдено.
И, наконец, в определении структуры is_enum мы отсекаем арифметические типы, передавая в качестве параметра классу std::bool_constant выражение decltype(test_enum<T>(0))::value && !std::is_arithmetic_v<T>.
Пример:
#include <iostream>
enum Enumeration { RED, GREEN, BLUE };
enum class AnotherEnumeration { BIG, SMALL };
struct MyStruct {
operator int() {
return 0;
}
};
int main() {
std::cout << is_enum<Enumeration>::value << std::endl;
std::cout << is_enum<AnotherEnumeration>::value << std::endl;
std::cout << is_enum<int>::value << std::endl;
std::cout << is_enum<MyStruct>::value << std::endl;
}
Вывод:
1
1
0
0
Стоит заметить, что если бы вместо
auto test_enum(int) ->
decltype(void(static_cast<TestEnum>(std::declval<T>())),
std::true_type{});
мы написали бы просто
auto test_enum(int) -> std::true_type;
то is_enum<MyStruct>::value имело бы значение true, ибо этот тип, хоть он и не является арифметическим или типом-перечислением, может быть неявно приведен к типу int.
В boost есть рабочая реализация is_enum для с++ 03.
Общая идея: enum не является ссылкой, арифметическим типом, классом, структурой, union, но при этом приводится к int. Массивы, функции и указатели не приводятся к int, арифметические типы определяются полным перечислением, ссылки не проблема, остается узнать, является ли тип классом или union. Только классы и union могут иметь указатели на члены, что позволяет решить задачу.
Упрощенная реализация для c++14:
#include <type_traits>
template< class... > using void_t = void;
template<class...> struct conjunction : std::true_type { };
template<class B1> struct conjunction<B1> : B1 { };
template<class B1, class... Bn>
struct conjunction<B1, Bn...>
: std::conditional_t<bool(B1::value), conjunction<Bn...>, B1> {};
template<class...> struct disjunction : std::false_type { };
template<class B1> struct disjunction<B1> : B1 { };
template<class B1, class... Bn>
struct disjunction<B1, Bn...>
: std::conditional_t<bool(B1::value), B1, disjunction<Bn...>> { };
template<class B>
struct negation : std::integral_constant<bool, !bool(B::value)> { };
template<class T, class = void_t<>> struct is_class_or_union: std::false_type{};
// Только у классов, структур и union могут быть указатели на члены
template<class T> struct is_class_or_union<T, void_t<void(T::*)(void)>>: std::true_type{};
template<class T> constexpr bool is_class_or_union_v = is_class_or_union<T>::value;
template<class T>
struct is_enum: public conjunction<
negation<
disjunction<
std::is_reference<T>,
std::is_arithmetic<T>,
is_class_or_union<T>
>
>,
std::is_convertible<T&, int>
>
{};
template<class T>
static constexpr bool is_enum_v = is_enum<T>::value;
enum TestEnum{};
enum TestEnumClass{};
struct TestStruct{
void method(){}
};
class TestClass{};
union TestUnion{
TestEnum te;
};
void TestFunction(){}
static_assert(is_enum_v<TestEnum>, "");
static_assert(is_enum_v<const TestEnum>, "");
static_assert(is_enum_v<TestEnumClass>, "");
static_assert(!is_enum_v<TestEnumClass&>, "");
static_assert(!is_enum_v<TestEnumClass*>, "");
static_assert(!is_enum_v<TestStruct>, "");
static_assert(!is_enum_v<TestClass>, "");
static_assert(!is_enum_v<TestUnion>, "");
static_assert(!is_enum_v<int>, "");
static_assert(!is_enum_v<decltype(&TestStruct::method)>, "");
static_assert(!is_enum_v<decltype(TestFunction)>, "");
static_assert(!is_enum_v<decltype(&TestFunction)>, "");
static_assert(!is_enum_v<int[1]>, "");
static_assert(!is_enum_v<TestEnumClass[0]>, "");