Индексирование массива (преобразование в целое число) с облачным перечислением - программирование
Подтвердить что ты не робот

Индексирование массива (преобразование в целое число) с облачным перечислением

Перечислители с индексом С++ 11 (синтаксис enum class) не преобразуются в целые числа, поэтому их нельзя использовать непосредственно в качестве индексов массива.

Каков наилучший способ получить преимущество при использовании их таким образом?

Я предоставил пару ответов, но, пожалуйста, добавьте больше идей!

4b9b3361

Ответ 1

Решение 1: Перегрузка оператора.

Это мой любимый. Перегрузите унарные operator+ и operator++, чтобы явно преобразовать в интегральный тип и приращение в указанном типе соответственно.

Используя шаблон enumeration_traits, можно активировать перегрузки, а не копировать шаблонный код. Но шаблон - всего лишь пара однострочных.

Код библиотеки (шаблоны, см. ниже для альтернативы без шаблонов):

template< typename e >
struct enumeration_traits;

struct enumeration_trait_indexing {
    static constexpr bool does_index = true;
};

template< typename e >
constexpr
typename std::enable_if< enumeration_traits< e >::does_index,
    typename std::underlying_type< e >::type >::type
operator + ( e val )
    { return static_cast< typename std::underlying_type< e >::type >( val ); }

template< typename e >
typename std::enable_if< enumeration_traits< e >::does_index,
    e & >::type
operator ++ ( e &val )
    { return val = static_cast< e >( + val + 1 ); }

Код пользователя:

enum class ducks { huey, dewey, louie, count };
template<> struct enumeration_traits< ducks >
    : enumeration_trait_indexing {};

double duck_height[ + ducks::count ];

Код котла (если он не используется в библиотеке, следует enum definition):

int operator + ( ducks val )
    { return static_cast< int >( val ); }

ducks &operator ++ ( ducks &val )
    { return val = static_cast< ducks >( + val + 1 ); }

Решение 2. Ручное определение области.

Синтаксис с облачным энтузиастом также работает с перечислениями без доступа (не enum class), которые неявно конвертируются в int. Скрытие перечисления внутри класса или пространства имен и импорт его с помощью typedef или using делает его псевдоопределенным.

Но если несколько перечислений попадают в одно и то же пространство имен, имена перечислителей могут сталкиваться, поэтому вы можете также использовать класс (или много пространств имен).

struct ducks_enum {
    enum ducks { huey, dewey, louie, count };
};
typedef ducks_enum::ducks ducks;

double duck_height[ ducks::count ]; // C++11
double duck_weight[ ducks_enum::count ]; // C++03

Это имеет некоторые преимущества. Он работает с С++ 03, но только с синтаксисом ducks_enum::count. Перечислители не облагаются внутри структуры и могут быть использованы в качестве базы для любого класса, который часто использует перечисления.

Ответ 2

Почему это сложнее, чем нужно, если ваше перечисление является последовательным?

enum class days
{
        monday,
        tuesday,
        wednesday,
        thursday,
        friday,
        saturday,
        sunday,
        count
};

....

const auto buffer_size = static_cast< std::size_t >( days::count );
char buffer[ buffer_size ];
buffer[ static_cast< std::size_t >( days::monday ) ] = 'M';

Или, если вы должны использовать шаблонные функции...

template< class enumeration >
constexpr std::size_t enum_count() noexcept
{
        static_assert( std::is_enum< enumeration >::value, "Not an enum" );
        return static_cast< std::size_t >( enumeration::count );
}

template< class enumeration >
constexpr std::size_t enum_index( const enumeration value ) noexcept
{
     static_assert( std::is_enum< enumeration >::value, "Not an enum" );
     return static_cast< std::size_t >( value )
}

...

char buffer[ enum_count< days >() ];
buffer[ enum_index( days::monday ) ] = 'M';

Ответ 3

Оригинальный вопрос, связанный с использованием перечисления в виде индекса массива. Вместо того, чтобы превращать перечисление в индекс для массива, создайте массив, который принимает перечисление как его индекс:

template <typename ValueType, typename Enumeration,
          Enumeration largest_enum = Enumeration::Count,
          int largest = static_cast <int> (largest_enum)>
class EnumeratedArray {
    ValueType underlying [static_cast <int> (largest_enum)];
public:
    using value_type = ValueType;
    using enumeration_type = Enumeration;
    EnumeratedArray () {
        for (int i = 0; i < largest; i++) {
            underlying [i] = ValueType {};
        }
    }
    inline ValueType &operator[] (const Enumeration index) {
        assert (static_cast <int> (index) >= 0 && static_cast <int> (index) < largest);
        return underlying [static_cast <const int> (index)];
    }
    inline const ValueType &operator[] (const Enumeration index) const {
        assert (static_cast <int> (index) >= 0 && static_cast <int> (index) < largest);
        return underlying [static_cast <const int> (index)];
    }
};

Итак, с примером более ранних уток:

enum class ducks { huey, dewey, louie, count };
EnumeratedArray<double, ducks, ducks::count> duck_height;
duck_height [ducks::huey] = 42.0;

Если значения уток были капитализированы по-разному, размер по умолчанию:

enum class Ducks { Huey, Dewey, Louie, Count };
EnumeratedArray<double, Ducks> duck_height;
duck_height [Ducks::Huey] = 42.0;

В дополнении к избежанию перечислений судорог, так как преобразование к индексу скрыто в реализации, перечисление не рискует ошибочно становится целым числом в других точках в коде, и не может вы случайно индексировать массив с помощью целого числа.

EnumeratedArray используется в pianod2, в src/common. Более обширная версия включает в себя шаблонную магию, чтобы явно явно инициализировать стандартные типы данных, конструктор для инициализации всех элементов с указанным значением и комментарии doc.

Ответ 4

Я реализую комбинацию решения DrTwox плюс безопасность типа решения Potatoswatter. Класс перечисления должен быть явно определен, чтобы разрешить индексирование, причем size() также определен:

#include <iostream>

template< typename T >
class EnumClassTraits;

struct EnumClassTraitIndexing {
    static constexpr bool does_index = true;
};

template<typename T>
constexpr
typename std::enable_if<EnumClassTraits<T>::does_index,
                        typename std::underlying_type<T>::type>::type enum_size() noexcept {
  return EnumClassTraits<T>::size();
}

template<typename T>
typename std::enable_if<EnumClassTraits<T>::does_index,
                        typename std::underlying_type<T>::type>::type enum_index(T enum_key) noexcept {
  return static_cast<typename std::underlying_type<T>::type>(enum_key);
}

enum class Days {Mon, Tue, Wed, Thu, Fri, Sat, Sun};

template<>
struct EnumClassTraits<Days> : EnumClassTraitIndexing {
  static constexpr std::underlying_type<Days>::type size() {
    return static_cast<std::underlying_type<Days>::type>(Days::Sun)+1;
  }
};

int main(int argc, char* argv[]) {
  Days days[enum_size<Days>()] = {Days::Mon, Days::Tue, Days::Wed, Days::Thu, Days::Fri, Days::Sat, Days::Sun};
  const char* days_to_string[enum_size<Days>()] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
  for (auto day : days) {
    std::cout << days_to_string[enum_index(day)] << std::endl;
  }
}

Ответ 5

В качестве альтернативы вы можете заменить array на map, что также означает, что вы можете избавиться от максимального перечисления, такого как count:

enum class days
{
    monday,
    tuesday,
    wednesday,
    thursday,
    friday,
    saturday,
    sunday
};


int main(int argc, char* argv[])
{
    std::map<days, const char*> days_to_string =
        {{days::monday, "Monday"},
        {days::tuesday, "Tuesday"},
        {days::wednesday, "Wednesday"},
        {days::thursday, "Thursday"},
        {days::friday, "Friday"}};

    for (auto day : days)
    {
        std::cout << days_to_string[day] << std::endl;
    }
}