강력한 형식의 열거 형을 int로 자동 변환하는 방법은 무엇입니까?


165
#include <iostream>

struct a {
  enum LOCAL_A { A1, A2 };
};
enum class b { B1, B2 };

int foo(int input) { return input; }

int main(void) {
  std::cout << foo(a::A1) << std::endl;
  std::cout << foo(static_cast<int>(b::B2)) << std::endl;
}

그만큼 a::LOCAL_A 강력한 형식의 열거 형이 달성하려고하는 것이지만 약간의 차이가 있습니다. 일반 열거 형은 정수 유형으로 변환 할 수 있지만 강력한 형식의 열거 형은 캐스트 없이는 할 수 없습니다.

그렇다면 강력한 유형의 열거 형 값을 캐스트없이 정수 유형으로 변환하는 방법이 있습니까? 그렇다면 어떻게?

답변:


134

질문에서 언급 한대로 문제를 해결할뿐만 아니라 여러 문제를 해결하기위한 강력한 유형의 열거 형 :

  1. 형식 안전을 제공하여 완전한 승격으로 정수로의 암시 적 변환을 제거합니다.
  2. 기본 유형을 지정하십시오.
  3. 강력한 범위 지정을 제공하십시오.

따라서 강력한 형식의 열거 형을 정수 또는 기본 형식으로 암시 적으로 변환하는 것은 불가능합니다. 따라서 static_cast변환을 명시 적으로 작성해야합니다.

유일한 문제가 범위를 정하고 정수로 암시 적 승격을 원한다면 선언 된 구조의 범위와 함께 강력하게 형식이 지정된 열거 형을 사용하는 것이 좋습니다.


2
그것은 C ++ 제작자들로부터 '우리가 원하는 것을 더 잘 알고있다'는 또 다른 이상한 예입니다. 기존의 (구식) 열거 형은 인덱스로의 암시 적 변환, 비트 단위 연산 등의 원활한 사용과 같은 많은 이점을 가졌습니다. 새로운 스타일의 열거 형은 정말 큰 범위 지정 기능을 추가했지만 ... 기본 유형 사양!). 이제는 구식 열거 형을 struct에 넣는 것과 같은 트릭을 사용하거나 std :: vector 주위에 자신의 래퍼를 만드는 것과 같은 새로운 열거 형에 대한 추악한 해결 방법을 만들어야합니다. 코멘트가 없습니다
avtomaton

152

다른 사람들이 말했듯이 암시 적 변환을 할 수 없으며 의도적으로 설계된 것입니다.

원하는 경우 캐스트에서 기본 유형을 지정할 필요가 없습니다.

template <typename E>
constexpr typename std::underlying_type<E>::type to_underlying(E e) noexcept {
    return static_cast<typename std::underlying_type<E>::type>(e);
}

std::cout << foo(to_underlying(b::B2)) << std::endl;

75

R. Martinho Fernandes 가 제공 한 C ++ 14 버전의 답변 은 다음과 같습니다.

#include <type_traits>

template <typename E>
constexpr auto to_underlying(E e) noexcept
{
    return static_cast<std::underlying_type_t<E>>(e);
}

이전 답변과 마찬가지로 모든 종류의 열거 형 및 기본 유형에서 작동합니다. noexcept예외가 발생하지 않으므로 키워드를 추가했습니다 .


업데이트
이것은 Scott Meyers의 Effective Modern C ++ 에도 나타납니다 . 항목 10을 참조하십시오 (이 책의 사본에있는 항목의 마지막 페이지에 자세히 나와 있습니다).


18
#include <cstdlib>
#include <cstdio>
#include <cstdint>

#include <type_traits>

namespace utils
{

namespace details
{

template< typename E >
using enable_enum_t = typename std::enable_if< std::is_enum<E>::value, 
                                               typename std::underlying_type<E>::type 
                                             >::type;

}   // namespace details


template< typename E >
constexpr inline details::enable_enum_t<E> underlying_value( E e )noexcept
{
    return static_cast< typename std::underlying_type<E>::type >( e );
}   


template< typename E , typename T>
constexpr inline typename std::enable_if< std::is_enum<E>::value &&
                                          std::is_integral<T>::value, E
                                         >::type 
 to_enum( T value ) noexcept 
 {
     return static_cast<E>( value );
 }

} // namespace utils




int main()
{
    enum class E{ a = 1, b = 3, c = 5 };

    constexpr auto a = utils::underlying_value(E::a);
    constexpr E    b = utils::to_enum<E>(5);
    constexpr auto bv = utils::underlying_value(b);

    printf("a = %d, b = %d", a,bv);
    return 0;
}

3
이것은 타이핑을 줄이거 나 코드를 더 깨끗하게 만들지 않으며 대규모 프로젝트에서 암시 적 변환을 찾기가 더 어려워지는 부작용이 있습니다. Static_cast는 이러한 구문보다 프로젝트 전체를 더 쉽게 검색 할 수 있습니다.
Atul Kumar

3
@AtulKumar to_enum을 검색하는 것보다 static_cast를 검색하는 것이 쉬운 방법은 무엇입니까?
Johann Gerell

1
이 답변에는 설명과 문서가 필요합니다.
궤도에서 가벼움 경주

17

아니요. 자연적인 방법없습니다 .

실제로 enum classC ++ 11에서 강력하게 입력 한 동기 중 하나 는으로의 자동 변환을 방지하는 것 int입니다.


Khurshid Normuradov의 답변을 살펴보십시오. 그것은 '자연스러운 길'이며 'C ++ Programming Language (4th ed.)'에서 의도 한대로입니다. 그것은 '자동적 인 방법'으로 나오지 않으며, 그것에 대해 좋습니다.
PapaAtHome

@PapaAtHome static_cast에 비해 그 이점을 이해하지 못합니다. 타이핑이나 코드 청결도에 큰 변화가 없습니다. 여기 자연적인 방법은 무엇입니까? 값을 반환하는 함수?
Atul Kumar

1
@ user2876962 나에게 이점은 Iammilind가 말한 것처럼 자동 또는 '자동'이 아니라는 것입니다. 따라서 오류를 찾기가 어렵습니다. 당신은 여전히 ​​캐스트를 할 수 있지만 그것에 대해 생각해야합니다. 그렇게 당신은 무엇을하고 있는지 알 수 있습니다. 나에게 그것은 '안전한 코딩'습관의 일부입니다. 전환이 자동으로 이루어지지 않는 것이 좋습니다. 오류가 발생할 가능성이 적습니다. 유형 시스템과 관련된 C ++ 11의 변경 사항 중 일부는이 범주에 해당합니다.
PapaAtHome

17

암묵적인 변환이없는 이유는 (설계에 의한) 다른 답변에서 제시되었습니다.

나는 개인적 operator+으로 열거 형 클래스에서 기본 유형으로 변환 하기 위해 단항 을 사용 합니다.

template <typename T>
constexpr auto operator+(T e) noexcept
    -> std::enable_if_t<std::is_enum<T>::value, std::underlying_type_t<T>>
{
    return static_cast<std::underlying_type_t<T>>(e);
}

"타이핑 오버 헤드"는 거의 없습니다.

std::cout << foo(+b::B2) << std::endl;

실제로 매크로를 사용하여 열거 형과 연산자 기능을 한 번에 생성합니다.

#define UNSIGNED_ENUM_CLASS(name, ...) enum class name : unsigned { __VA_ARGS__ };\
inline constexpr unsigned operator+ (name const val) { return static_cast<unsigned>(val); }

13

이것이 당신이나 다른 누군가를 돕기를 바랍니다.

enum class EnumClass : int //set size for enum
{
    Zero, One, Two, Three, Four
};

union Union //This will allow us to convert
{
    EnumClass ec;
    int i;
};

int main()
{
using namespace std;

//convert from strongly typed enum to int

Union un2;
un2.ec = EnumClass::Three;

cout << "un2.i = " << un2.i << endl;

//convert from int to strongly typed enum
Union un;
un.i = 0; 

if(un.ec == EnumClass::Zero) cout << "True" << endl;

return 0;
}

33
이것을 "타입 punning"이라고하며 C ++ 표준에서 설정 한 후에 un.i는 "활성 멤버"이고 공용체의 활성 멤버 만 읽을 수 있다고 C ++ 표준에 따라 일부 컴파일러에서 지원하는 것은 이식성이 없습니다 .
Jonathan Wakely

6
@JonathanWakely 당신은 기술적으로 정확하지만, 이것이 제대로 작동하지 않는 컴파일러를 본 적이 없습니다. 이와 같은 것, 익명 조합 및 #pragma는 사실상 표준입니다.
BigSandwich

5
단순 출연자가 할 때 표준에서 명시 적으로 금지하는 것을 사용하는 이유는 무엇입니까? 이것은 단지 잘못이다.
Paul Groke

1
기술적으로 올바른지 여부는 다른 솔루션보다 더 읽기 쉽습니다. 그리고 나에게 더 중요한 것은 직렬화뿐만 아니라 열거 형 클래스의 직렬화 해제 및 읽기 쉬운 형식을 해결하는 데 사용할 수 있습니다.
Marcin Waśniowski 2016 년

6
나는이 지저분한 정의되지 않은 행동을 단순한 것보다 "더 읽기 쉽다"고 생각하는 사람들이 있다는 사실에 절망한다 static_cast.
underscore_d

13

짧은 대답은 위의 게시물이 지적한 것처럼 할 수 없다는 것입니다. 그러나 제 경우에는 단순히 네임 스페이스를 어지럽히고 싶지 않지만 여전히 암시 적 변환을하고 싶었습니다.

#include <iostream>

using namespace std;

namespace Foo {
   enum Foo { bar, baz };
}

int main() {
   cout << Foo::bar << endl; // 0
   cout << Foo::baz << endl; // 1
   return 0;
}

네임 스페이스를 사용하면 열거 형 값을 기본 유형으로 정적 캐스트 할 필요가없는 동안 유형 안전 레이어가 추가됩니다.


3
유형 안전성을 추가하지 않습니다 (실제로 유형 안전성을 제거했습니다)-이름 범위 만 추가합니다.
궤도에서 가벼움 경주

@LightnessRacesinOrbit 예 동의합니다. 나는 거짓말했다. 엄밀히 말하면, 형식은 네임 스페이스 / 범위 바로 아래에 있고에 적합합니다 Foo::Foo. 회원으로 액세스 할 수 있습니다 Foo::barFoo::baz암묵적으로 (많은 종류의 안전을 그렇게하지 않음) 주조 할 수있다. 특히 새 프로젝트를 시작할 때 거의 항상 열거 형 클래스를 사용하는 것이 좋습니다.
solstice333 1

6

이 기본 불가능한 것 같다 enum class, 그러나 아마 당신은 조롱 수 enum class로모그래퍼class .

이 경우

enum class b
{
    B1,
    B2
};

다음과 같습니다.

class b {
 private:
  int underlying;
 public:
  static constexpr int B1 = 0;
  static constexpr int B2 = 1;
  b(int v) : underlying(v) {}
  operator int() {
      return underlying;
  }
};

이것은 대부분 원본과 동일합니다 enum class. b::B1return type 함수에서 직접 리턴 할 수 있습니다 b. 넌 할 수있어switch case 그것으로 .

그리고이 예제의 정신에서 템플릿 (다른 것들과 함께)을 사용하여 enum class구문에 의해 정의 된 가능한 객체를 일반화하고 조롱 할 수 있습니다 .


그러나 B1과 B2는 클래스 외부에서 정의해야합니다 ... 또는 case-header.h <
-class

"static constexpr int '대신"static constexpr b "가 아니어야합니까? 그렇지 않으면 b :: B1은 형식 안전성이 전혀없는 int 일뿐입니다
Some Guy

4

많은 사람들이 말했듯이 오버 헤드와 너무 많은 복잡성을 추가하지 않고 자동으로 변환하는 방법은 없지만 시나리오에서 일부 캐스트가 약간 많이 사용되는 경우 람다를 사용하여 타이핑을 줄이고 더 좋아 보일 수 있습니다. 약간의 함수 오버 헤드 호출이 추가되지만 아래에서 볼 수 있듯이 긴 static_cast 문자열과 비교하여 코드를 더 읽기 쉽게 만듭니다. 프로젝트 전체에 유용하지는 않지만 클래스 전체에만 유용 할 수 있습니다.

#include <bitset>
#include <vector>

enum class Flags { ......, Total };
std::bitset<static_cast<unsigned int>(Total)> MaskVar;
std::vector<Flags> NewFlags;

-----------
auto scui = [](Flags a){return static_cast<unsigned int>(a); };

for (auto const& it : NewFlags)
{
    switch (it)
    {
    case Flags::Horizontal:
        MaskVar.set(scui(Flags::Horizontal));
        MaskVar.reset(scui(Flags::Vertical)); break;
    case Flags::Vertical:
        MaskVar.set(scui(Flags::Vertical));
        MaskVar.reset(scui(Flags::Horizontal)); break;

   case Flags::LongText:
        MaskVar.set(scui(Flags::LongText));
        MaskVar.reset(scui(Flags::ShorTText)); break;
    case Flags::ShorTText:
        MaskVar.set(scui(Flags::ShorTText));
        MaskVar.reset(scui(Flags::LongText)); break;

    case Flags::ShowHeading:
        MaskVar.set(scui(Flags::ShowHeading));
        MaskVar.reset(scui(Flags::NoShowHeading)); break;
    case Flags::NoShowHeading:
        MaskVar.set(scui(Flags::NoShowHeading));
        MaskVar.reset(scui(Flags::ShowHeading)); break;

    default:
        break;
    }
}

2

C ++위원회는 한 단계 앞으로 (글로벌 네임 스페이스에서 열거 범위를 지정 함) 50 단계를 다시 거쳤습니다 (정수로 열거 형식이 소멸되지 않음). 슬프게도enum class 열거 형의 가치가 비 상징적 인 방법으로 필요한 경우 단순히 사용할 수 없습니다.

가장 좋은 해결책은 전혀 사용하지 않고 네임 스페이스 또는 구조체를 사용하여 열거 형을 직접 지정하는 것입니다. 이를 위해 교환이 가능합니다. 열거 형 자체를 참조 할 때 약간의 추가 입력이 필요하지만 종종 그렇지는 않습니다.

struct TextureUploadFormat {
    enum Type : uint32 {
        r,
        rg,
        rgb,
        rgba,
        __count
    };
};

// must use ::Type, which is the extra typing with this method; beats all the static_cast<>()
uint32 getFormatStride(TextureUploadFormat::Type format){
    const uint32 formatStride[TextureUploadFormat::__count] = {
        1,
        2,
        3,
        4
    };
    return formatStride[format]; // decays without complaint
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.