C ++에서 int를 enum으로 캐스트하는 일반적인 방법


81

캐스팅에 일반적인 방법이 있나요 intenum에가 C++?

경우 int의 범위 내에 enum그것이 반환해야합니다 enum, 값을 그렇지 않으면 던져 exception. 일반적으로 작성하는 방법이 있습니까? 둘 이상 enum type이 지원되어야합니다.

배경 : 외부 열거 형 유형이 있고 소스 코드를 제어 할 수 없습니다 . 이 값을 데이터베이스에 저장하고 검색하고 싶습니다.


enum e{x = 10000};이 경우 9999범위에 속합니까 enum?
Armen Tsirunyan 2010

아니, 9999떨어지지 않습니다.
Leonid

9
좋은 질문. "왜?" 이것이 나타날 것입니다. "역 직렬화"라고 말하겠습니다. 저에게는 충분한 이유가 될 것 같습니다. .NET에 대한 C ++ 0x- 컴파일 답변도 듣고 싶습니다 enum class.
Kos

9
여기서 "범위"는 잘못된 단어입니다. "도메인"일까요?
Constantin

boost :: numeric_cast <>는 값이 범위를 벗어난 경우 양수 또는 음수 오버플로 예외를 throw합니다. 그러나 열거 형 유형에도 좋은지 확실하지 않습니다. 시도해 볼 수 있습니다.
yasouser

답변:


37

분명한 것은 열거 형에 주석을 추가하는 것입니다.

// generic code
#include <algorithm>

template <typename T>
struct enum_traits {};

template<typename T, size_t N>
T *endof(T (&ra)[N]) {
    return ra + N;
}

template<typename T, typename ValType>
T check(ValType v) {
    typedef enum_traits<T> traits;
    const T *first = traits::enumerators;
    const T *last = endof(traits::enumerators);
    if (traits::sorted) { // probably premature optimization
        if (std::binary_search(first, last, v)) return T(v);
    } else if (std::find(first, last, v) != last) {
        return T(v);
    }
    throw "exception";
}

// "enhanced" definition of enum
enum e {
    x = 1,
    y = 4,
    z = 10,
};

template<>
struct enum_traits<e> {
    static const e enumerators[];
    static const bool sorted = true;
};
// must appear in only one TU,
// so if the above is in a header then it will need the array size
const e enum_traits<e>::enumerators[] = {x, y, z};

// usage
int main() {
    e good = check<e>(1);
    e bad = check<e>(2);
}

배열을 최신 상태로 유지해야하는데 e, .NET의 작성자가 아닌 경우 이는 성가신 일입니다 e. Sjoerd가 말했듯이 괜찮은 빌드 시스템으로 자동화 할 수 있습니다.

어쨌든, 당신은 7.2 / 6입니다.

emin이 가장 작은 열거 자이고 emax가 가장 큰 열거 형의 경우 열거 형 값은 bmin에서 bmax 범위의 기본 유형 값이며, 여기서 bmin 및 bmax는 각각 가장 작은 값의 최소값과 최대 값입니다 emin과 emax를 저장할 수있는 비트 필드. 열거 자에 의해 정의되지 않은 값이있는 열거를 정의 할 수 있습니다.

따라서의 작성자 e가 아닌 경우의 유효한 값이 e실제로 정의에 표시 된다는 보장이 없을 수도 있습니다 .


22

추한.

enum MyEnum { one = 1, two = 2 };

MyEnum to_enum(int n)
{
  switch( n )
  {
    case 1 :  return one;
    case 2 : return two;
  }
  throw something();
}

이제 진짜 질문입니다. 왜 이것이 필요합니까? 코드는보기 흉하고 작성하기도 쉽지 않으며 (*?) 유지 관리하기도 쉽지 않으며 코드에 통합하기도 쉽지 않습니다. 그것이 틀렸다는 것을 알려주는 코드. 왜 싸울까요?

편집하다:

또는 열거 형이 C ++에서 정수 유형 인 경우 :

enum my_enum_val = static_cast<MyEnum>(my_int_val);

그러나 이것은 위의 것보다 더 추하고 오류가 발생하기 쉽고 원하는대로 던지지 않습니다.


이것은 MyEnum이라는 하나의 유형 만 지원합니다.
Simone

2
@Leonid : 내 지식으로는 일반적으로 할 수 없습니다. 어떤 수준에서는 throw유효하지 않은 유형에 대해 (또는 특별한 작업을 수행 하는) 모든 솔루션에 제가 게시 한 것과 같은 스위치가 있어야합니다.
John Dibling

2
이것이 -1'ed 인 이유는 무엇입니까? 정답입니다. 일부 사람들이 바라고있는 대답이 아니라고해서 그것이 틀렸다는 의미는 아닙니다.
John Dibling

12
A static_cast<MyEnum>도 작동하며 reinterpret_cast<MyEnum>
Sjoerd

1
이 접근 방식은 잘 작동하며 도구를 사용하여 함수를 생성하는 경우 더 좋습니다.
Nick

3

설명했듯이 값이 데이터베이스에있는 경우이 테이블을 읽고 열거 형과 to_enum(int)함수 를 모두 사용하여 .h 및 .cpp 파일을 만드는 코드 생성기를 작성하는 것이 어떻습니까?

장점 :

  • to_string(my_enum)기능 추가가 쉽습니다 .
  • 유지 보수가 거의 필요하지 않음
  • 데이터베이스와 코드가 동기화 됨

열거 형 소스 코드 는 제어 할 수 없습니다 . 전환을 구현하는 시설을 생성 할 수 있다는 데 동의합니다. 그러나 시설은 외부 열거 형 에 대한 변경 / 확장을 인식하지 못합니다 (컴파일시 매번 실행되지 않는 한).
Leonid

@Leonid 그런 다음 해당 열거 형 헤더를 읽고이를 to_enum(int)기반으로 함수를 생성합니다 .
Sjoerd

@Leonid 모든 진지한 프로젝트 관리 시스템은 심지어 make두 파일의 날짜를 비교하여 생성기를 다시 실행해야하는지 여부를 확인할 수 있습니다.
Sjoerd

지금은 발전기에 대한 더 간단한 솔루션으로 갈 것이라고 생각합니다. 그러나 아이디어에 감사드립니다.
Leonid

우리는 직장에서이 체계를 사용합니다. 도구는 템플릿에서 .hpp 코드를 생성하며 템플릿은 최소한입니다.
Nick

3

C ++에는 내부 검사가 없으며 "도메인 검사"기능이 내장되어 있지 않습니다.


2

이것에 대해 어떻게 생각하세요?

#include <iostream>
#include <stdexcept>
#include <set>
#include <string>

using namespace std;

template<typename T>
class Enum
{
public:
    static void insert(int value)
    {
        _set.insert(value);
    }

    static T buildFrom(int value)
    {
        if (_set.find(value) != _set.end()) {
            T retval;
            retval.assign(value);
            return retval;
        }
        throw std::runtime_error("unexpected value");
    }

    operator int() const { return _value; }

private:
    void assign(int value)
    {
        _value = value;
    }

    int _value;
    static std::set<int> _set;
};

template<typename T> std::set<int> Enum<T>::_set;

class Apples: public Enum<Apples> {};

class Oranges: public Enum<Oranges> {};

class Proxy
{
public:
    Proxy(int value): _value(value) {}

    template<typename T>
    operator T()
    {
        T theEnum;
        return theEnum.buildFrom(_value);
    }

    int _value;
};

Proxy convert(int value)
{
    return Proxy(value);
}

int main()
{    
    Apples::insert(4);
    Apples::insert(8);

    Apples a = convert(4); // works
    std::cout << a << std::endl; // prints 4

    try {
        Apples b = convert(9); // throws    
    }
    catch (std::exception const& e) {
        std::cout << e.what() << std::endl; // prints "unexpected value"
    }
    try {
        Oranges b = convert(4); // also throws  
    }
    catch (std::exception const& e) {
        std::cout << e.what() << std::endl; // prints "unexpected value"
    }
}

그런 다음 여기 에 게시 한 코드를 사용 하여 값을 전환 할 수 있습니다.


여전히 Apples::insert(4)어딘가에 추가해야 하므로 스위치에 비해 이점이 없습니다.
Sjoerd

1
그는 값이 데이터베이스에서 나온다고 말 했으므로 "삽입"메소드를 추가했습니다.
Simone

1

설명하는 것과 같은 것이 존재하는 것을 원하지 않아야합니다. 코드 디자인에 문제가있는 것 같습니다.

또한 열거 형이 범위에 있다고 가정하지만 항상 그런 것은 아닙니다.

enum Flags { one = 1, two = 2, four = 4, eigh = 8, big = 2000000000 };

이것은 범위에 있지 않습니다. 가능하더라도 0에서 2 ^ n까지의 모든 정수를 검사하여 열거 형 값과 일치하는지 확인해야합니까?


그렇지 않으면 데이터베이스에서 열거 형 값을 어떻게 검색합니까? 정수는 컴파일 타임에 알려져 있는데 왜 템플릿을 기반으로하는 일반 변환이 불가능할까요?
Leonid

2
@Leonid : 어떤 수준에서는 내가 말했듯이 스위치가 필요하기 때문입니다.
John Dibling

2
@Leonid 템플릿은 생각할 수있는 모든 문제를 해결하는 은색 총알이 아닙니다.
Sjoerd

존이 옳다. 원하는 작업을 수행하려면 열거 형보다 복잡한 유형이 필요합니다. 클래스 계층 구조에서 가능하다고 생각합니다.
Simone

클래스 계층 구조를 사용하는 솔루션을 게시했습니다.
Simone

1

열거 형 값을 템플릿 매개 변수로 나열 할 준비가 되었으면 C ++ 11에서 varadic 템플릿을 사용하여이를 수행 할 수 있습니다. 이를 좋은 것으로 볼 수 있으며, 다른 컨텍스트에서 유효한 열거 형 값의 하위 집합을 허용 할 수 있습니다. 외부 소스의 코드를 구문 분석 할 때 종종 유용합니다.

원하는만큼 일반적이지는 않지만 검사 코드 자체는 일반화되어 있으므로 값 집합을 지정하기 만하면됩니다. 이 접근 방식은 간격, 임의의 값 등을 처리합니다.

template<typename EnumType, EnumType... Values> class EnumCheck;

template<typename EnumType> class EnumCheck<EnumType>
{
public:
    template<typename IntType>
    static bool constexpr is_value(IntType) { return false; }
};

template<typename EnumType, EnumType V, EnumType... Next>
class EnumCheck<EnumType, V, Next...> : private EnumCheck<EnumType, Next...>
{
    using super = EnumCheck<EnumType, Next...>;

public:
    template<typename IntType>
    static bool constexpr is_value(IntType v)
    {
        return v == static_cast<typename std::underlying_type<EnumType>::type>(V) || super::is_value(v);
    }

    EnumType convert(IntType v)
    {
        if (!is_value(v)) throw std::runtime_error("Enum value out of range");
        return static_cast<EnumType>(v);
};

enum class Test {
    A = 1,
    C = 3,
    E = 5
};

using TestCheck = EnumCheck<Test, Test::A, Test::C, Test::E>;

void check_value(int v)
{
    if (TestCheck::is_value(v))
        printf("%d is OK\n", v);
    else
        printf("%d is not OK\n", v);
}

int main()
{
    for (int i = 0; i < 10; ++i)
        check_value(i);
}

이 링크가 질문에 답할 수 있지만 여기에 답변의 필수 부분을 포함하고 참조 용 링크를 제공하는 것이 좋습니다. 링크 된 페이지가 변경되면 링크 전용 답변이 무효화 될 수 있습니다. - 리뷰에서
Tas

1
@Tas 다른 SO 답변에 대한 링크입니다-외부 링크와 동일한 문제가 없습니다. 어쨌든 업데이트되었습니다.
janm

0

"추악한"버전 대신 C ++ 0x는 여러 열거 형을 허용합니다. 스위치보다는 이니셜 라이저 목록을 사용합니다. 불행히도 이것은 열거 형 값을 하드 코딩해야하는 필요성을 해결하지 못합니다.

#include <cassert>  // assert

namespace  // unnamed namespace
{
    enum class e1 { value_1 = 1, value_2 = 2 };
    enum class e2 { value_3 = 3, value_4 = 4 };

    template <typename T>
    int valid_enum( const int val, const T& vec )
    {
        for ( const auto item : vec )
            if ( static_cast<int>( item ) == val ) return val;

        throw std::exception( "invalid enum value!" );  // throw something useful here
    }   // valid_enum
}   // ns

int main()
{
    // generate list of valid values
    const auto e1_valid_values = { e1::value_1, e1::value_2 };
    const auto e2_valid_values = { e2::value_3, e2::value_4 };

    auto result1 = static_cast<e1>( valid_enum( 1, e1_valid_values ) );
    assert( result1 == e1::value_1 );

    auto result2 = static_cast<e2>( valid_enum( 3, e2_valid_values ) );
    assert( result2 == e2::value_3 );

    // test throw on invalid value
    try
    {
        auto result3 = static_cast<e1>( valid_enum( 9999999, e1_valid_values ) );
        assert( false );
    }
    catch ( ... )
    {
        assert( true );
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.