Как я могу вывести значение класса перечисления в C ++ 11

101

Как я могу вывести значение enum classв C ++ 11? В C ++ 03 это так:

#include <iostream>

using namespace std;

enum A {
  a = 1,
  b = 69,
  c= 666
};

int main () {
  A a = A::c;
  cout << a << endl;
}

в c ++ 0x этот код не компилируется

#include <iostream>

using namespace std;

enum class A {
  a = 1,
  b = 69,
  c= 666
};

int main () {
  A a = A::c;
  cout << a << endl;
}


prog.cpp:13:11: error: cannot bind 'std::ostream' lvalue to 'std::basic_ostream<char>&&'
/usr/lib/gcc/i686-pc-linux-gnu/4.5.1/../../../../include/c++/4.5.1/ostream:579:5: error:   initializing argument 1 of 'std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = char, _Traits = std::char_traits<char>, _Tp = A]'

скомпилировано на Ideone.com

Ади
источник
1
Почему вы пытаетесь вывести enum? Класс enum используется, чтобы не
путать

Ответы:

126

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

std::cout << static_cast<std::underlying_type<A>::type>(a) << std::endl;

Вы можете захотеть инкапсулировать логику в шаблон функции:

template <typename Enumeration>
auto as_integer(Enumeration const value)
    -> typename std::underlying_type<Enumeration>::type
{
    return static_cast<typename std::underlying_type<Enumeration>::type>(value);
}

используется в качестве:

std::cout << as_integer(a) << std::endl;
Джеймс МакНеллис
источник
3
Есть ли причина, по которой здесь используется синтаксис конечного возвращаемого типа?
Nicol Bolas
3
@NicolBolas: Я скопировал as_integerс одного из моих открытых источников библиотек, CxxReflect (см enumeration.hpp ). Библиотека последовательно и везде использует конечные возвращаемые типы. Для последовательности.
Джеймс МакНеллис
11
Хотя это и происходит с опозданием на 2 года, в случае, если кто-то еще увидит этот вопрос, вы можете просто использовать метод приведенного выше метода приведения и просто вызвать «static_cast <int> (value)», чтобы получить целое число или «static_cast <A> (intValue)» для получить значение перечисления. Просто имейте в виду, что переход от int к enum или enum к enum может вызвать проблемы и, как правило, является признаком ошибки дизайна.
Бенджамин Дэнджер Джонсон
4
int (значение) и A (intValue) также работают без уродливых угловых скобок.
Grault
4
as_integerможно определить constexprтак, чтобы его можно было использовать в контекстах, где необходимо постоянное выражение.
Nawaz
40
#include <iostream>
#include <type_traits>

using namespace std;

enum class A {
  a = 1,
  b = 69,
  c= 666
};

std::ostream& operator << (std::ostream& os, const A& obj)
{
   os << static_cast<std::underlying_type<A>::type>(obj);
   return os;
}

int main () {
  A a = A::c;
  cout << a << endl;
}
ForEveR
источник
Я скопировал этот пример дословно и скомпилировал как, g++ -std=c++0x enum.cppно получаю кучу ошибок компилятора -> pastebin.com/JAtLXan9 . Я также не смог скомпилировать пример от @ james-mcnellis.
Деннис
4
@Dennis underlying_type только в C ++ 11
Deqing
23

Можно заставить ваш второй пример (то есть тот, который использует перечисление с ограниченной областью действия) работать с использованием того же синтаксиса, что и перечисления без области действия. Кроме того, решение является универсальным и будет работать для всех перечислений с областью действия, а не для написания кода для каждого перечисления с областью действия (как показано в ответе, предоставленном @ForEveR ).

Решение состоит в том, чтобы написать универсальную operator<<функцию, которая будет работать для любого перечисления с ограниченной областью видимости. Решение использует SFINAE via std::enable_ifи выглядит следующим образом.

#include <iostream>
#include <type_traits>

// Scoped enum
enum class Color
{
    Red,
    Green,
    Blue
};

// Unscoped enum
enum Orientation
{
    Horizontal,
    Vertical
};

// Another scoped enum
enum class ExecStatus
{
    Idle,
    Started,
    Running
};

template<typename T>
std::ostream& operator<<(typename std::enable_if<std::is_enum<T>::value, std::ostream>::type& stream, const T& e)
{
    return stream << static_cast<typename std::underlying_type<T>::type>(e);
}

int main()
{
    std::cout << Color::Blue << "\n";
    std::cout << Vertical << "\n";
    std::cout << ExecStatus::Running << "\n";
    return 0;
}
Джеймс Адкисон
источник
Вам нужен typenameраньше std::underlying_type<T>::type.
uckelman
@uckelman Вы абсолютно правы. Спасибо, что обновили мой ответ.
Джеймс Адкисон
это сработало для меня под clang, но в gcc 4.9.2 это решение не работает при объединении << вместе с ошибкой error: cannot bind ‘std::basic_ostream<char>’ lvalue to ‘std::basic_ostream<char>&&’. это похоже на то, что, когда поток является временным, ADL не работает, и указанный выше шаблон невозможен. какие-нибудь советы?
ofloveandhate
@ofloveandhate Не могли бы вы дать ссылку на пример, в котором возникает проблема? Я протестировал приведенный выше код в gcc 4.9.2 без каких-либо проблем и с небольшими изменениями, я преобразовал 3 coutоператора в один coutоператор, связав <<операторы вместе. Смотрите здесь
Джеймс Адкисон
позвольте мне пересмотреть мое заявление. Я пытался напечатать класс перечисления, содержащийся внутри класса, извне этого класса. приведенный выше код действительно работает для классов enum, не содержащихся в самих классах.
ofloveandhate
10

(Мне пока не разрешено комментировать.) Я бы предложил следующие улучшения и без того отличного ответа Джеймса Макнеллиса:

template <typename Enumeration>
constexpr auto as_integer(Enumeration const value)
    -> typename std::underlying_type<Enumeration>::type
{
    static_assert(std::is_enum<Enumeration>::value, "parameter is not of type enum or enum class");
    return static_cast<typename std::underlying_type<Enumeration>::type>(value);
}

с участием

  • constexpr: позволяет мне использовать значение члена перечисления как размер массива времени компиляции
  • static_assert+ is_enum: чтобы во время компиляции "гарантировать" выполнение функцией sth. только с перечислениями, как предлагается

Кстати, я спрашиваю себя: зачем мне вообще использовать, enum classкогда я хотел бы присвоить числовые значения членам моего перечисления ?! Учитывая усилия по конверсии.

Возможно, я бы тогда вернулся к обычному, enumкак я предлагал здесь: как использовать перечисления в качестве флагов в C ++?


Еще один (лучший) вариант без static_assert, основанный на предложении @TobySpeight:

template <typename Enumeration>
constexpr std::enable_if_t<std::is_enum<Enumeration>::value,
std::underlying_type_t<Enumeration>> as_number(const Enumeration value)
{
    return static_cast<std::underlying_type_t<Enumeration>>(value);
}
яу
источник
Есть ли тип, Tкоторый std::underlying_type<T>::typeсуществует, но std::is_enum<T>::valueявляется ложным? Если нет, то static_assertценность не увеличивается.
Тоби Спейт
1
Я не тестировал на всех компиляторах. Но, @TobySpeight, вы, вероятно, правы, msvc2013, кажется, выплевывает понятные сообщения об ошибках, предполагая соответствие один-к-одному между существующим базовым_типом и самим типом, являющимся перечислением. И static_assert даже не запускается. Но: в ссылке говорится, что поведение базового_типа не определено, если ему не предоставлен полный тип перечисления. Так что static_assert - это всего лишь надежда на получение максимально понятного сообщения на всякий случай. Возможно, есть возможности заставить его обрабатывать раньше / раньше?
yau
Ах да, вы правы, что это undefined, если Enumerationэто не полный тип перечисления. В этом случае может быть уже слишком поздно, поскольку он используется в возвращаемом типе. Может, мы могли бы указать std::enable_if<std::is_enum<Enumeration>::value, std::underlying_type<Enumeration>::type>в качестве возвращаемого типа? Конечно, это намного проще (и сообщения об ошибках намного яснее), если у вас есть компилятор с поддержкой концепций ...
Тоби Спейт
8

Чтобы писать проще,

enum class Color
{
    Red = 1,
    Green = 11,
    Blue = 111
};

int value = static_cast<int>(Color::Blue); // 111
Аудрюс Мескаускас
источник
Это не сработает, если перечислению явно задан базовый тип
Джеймс,
3

Следующее сработало для меня в С ++ 11:

template <typename Enum>
constexpr typename std::enable_if<std::is_enum<Enum>::value,
                                  typename std::underlying_type<Enum>::type>::type
to_integral(Enum const& value) {
    return static_cast<typename std::underlying_type<Enum>::type>(value);
}
NutCracker
источник
0

Вы можете сделать что-то вроде этого:

//outside of main
namespace A
{
    enum A
    {
        a = 0,
        b = 69,
        c = 666
    };
};

//in main:

A::A a = A::c;
std::cout << a << std::endl;
Граф Лемонраб
источник
2
Заданный вопрос о классе перечисления.
Ant