Реализация is_enum

Каким способом можно реализовать std::is_enum? Я смотрел реализации в libstdc++ и libcxx, но и там и там используются интринсики компилятора. Как можно реализовать эту функцию без их использования?


Ответы (2 шт):

Автор решения: Eoan Ermine

Благодаря тому, что в 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.

→ Ссылка
Автор решения: Ariox

В 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]>, "");
→ Ссылка