Cách chung để ép kiểu int sang enum trong C ++


81

Có một cách chung chung để cast intđể enumC++?

Nếu intnằm trong phạm vi của một, enumnó sẽ trả về một enumgiá trị, nếu không thì ném một exception. Có cách nào để viết nó một cách chung chung ? Nhiều hơn một enum typenên được hỗ trợ.

Thông tin cơ bản: Tôi có một loại enum bên ngoài và không kiểm soát được mã nguồn. Tôi muốn lưu trữ giá trị này trong cơ sở dữ liệu và truy xuất nó.


enum e{x = 10000};không trong trường hợp này 9999rơi vào phạm vi của enum?
Armen Tsirunyan

Không, 9999không rơi.
Leonid

9
Câu hỏi hay. Đối với bất kỳ "tại sao?" sẽ xuất hiện, hãy để tôi chỉ nói "deserialization" - có vẻ như đủ lý do cho tôi. Tôi cũng rất vui khi nghe câu trả lời phù hợp với C ++ 0x cho enum class.
Kos,

9
"Phạm vi" là từ sai ở đây, có thể là "miền"?
Constantin

boost :: numeric_cast <> ném ra một ngoại lệ tràn tích cực hoặc tiêu cực nếu giá trị nằm ngoài giới hạn. Nhưng không chắc liệu nó có tốt cho các loại enum hay không. Bạn có thể thử điều đó.
yasouser

Câu trả lời:


37

Điều hiển nhiên là chú thích enum của bạn:

// 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);
}

Bạn cần cập nhật mảng e, điều này sẽ gây phiền toái nếu bạn không phải là tác giả của e. Như Sjoerd nói, nó có thể được tự động hóa với bất kỳ hệ thống xây dựng tốt nào.

Trong mọi trường hợp, bạn đang chống lại mức 7.2 / 6:

Đối với kiểu liệt kê trong đó emin là điều tra viên nhỏ nhất và emax là lớn nhất, các giá trị của kiểu liệt kê là giá trị của kiểu cơ bản trong phạm vi bmin đến bmax, trong đó bmin và bmax lần lượt là giá trị nhỏ nhất và lớn nhất của giá trị nhỏ nhất trường bit có thể lưu trữ emin và emax. Có thể xác định một kiểu liệt kê có các giá trị không được xác định bởi bất kỳ kiểu liệt kê nào của nó.

Vì vậy, nếu bạn không phải là tác giả của e, bạn có thể có hoặc không có đảm bảo rằng các giá trị hợp lệ ethực sự xuất hiện trong định nghĩa của nó.


22

Xấu xí.

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

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

Bây giờ cho câu hỏi thực sự. Tại sao bạn cần cái này? Mã xấu, không dễ viết (*?), Không dễ bảo trì và không dễ kết hợp với mã của bạn. Mã nó cho bạn biết rằng nó sai. Tại sao phải chiến đấu với nó?

BIÊN TẬP:

Ngoài ra, do enum là kiểu tích phân trong C ++:

enum my_enum_val = static_cast<MyEnum>(my_int_val);

nhưng điều này thậm chí còn xấu hơn ở trên, dễ bị lỗi hơn nhiều và nó sẽ không ném như bạn mong muốn.


điều này chỉ hỗ trợ một loại, MyEnum.
Simone

2
@Leonid: Theo hiểu biết của tôi, nó không thể được thực hiện một cách chung chung. Ở một mức độ nào đó, bất kỳ giải pháp nào mà bạn đưa ra sẽ throw(hoặc làm bất cứ điều gì đặc biệt) cho các loại không hợp lệ phải có công tắc như tôi đã đăng.
John Dibling

2
Tại sao điều này là -1'ed? Đó là câu trả lời chính xác. Chỉ vì nó không phải là câu trả lời mà một số người hy vọng không có nghĩa là nó sai.
John Dibling

12
A static_cast<MyEnum>cũng sẽ hoạt động, và nên được ưu tiên hơnreinterpret_cast<MyEnum>
Sjoerd

1
Cách tiếp cận này hoạt động tốt và thậm chí còn tốt hơn nếu bạn sử dụng một công cụ để tạo hàm.
Nick

3

Như bạn mô tả, nếu các giá trị nằm trong cơ sở dữ liệu, tại sao không viết một trình tạo mã đọc bảng này và tạo tệp .h và .cpp với cả enum và một to_enum(int)hàm?

Ưu điểm:

  • Dễ dàng thêm một to_string(my_enum)chức năng.
  • Yêu cầu bảo trì ít
  • Cơ sở dữ liệu và mã đồng bộ

Không có quyền kiểm soát mã nguồn kiểu enum . Tôi đồng ý rằng có thể tạo ra một cơ sở thực hiện chuyển đổi. Tuy nhiên, cơ sở sẽ không biết về bất kỳ thay đổi / phần mở rộng nào được thực hiện đối với kiểu enum bên ngoài (trừ khi được thực thi mọi lúc tại thời điểm biên dịch).
Leonid

@Leonid Sau đó đọc tiêu đề enum đó và tạo to_enum(int)hàm dựa trên đó.
Sjoerd

@Leonid Mọi hệ thống quản lý dự án nghiêm túc, thậm chí make, có thể so sánh ngày của hai tệp để xem liệu trình tạo có phải được chạy lại hay không.
Sjoerd

Tôi nghĩ rằng tôi sẽ đi với một giải pháp đơn giản hơn cho máy phát điện ngay bây giờ. Nhưng cảm ơn vì ý tưởng.
Leonid

Chúng tôi sử dụng lược đồ này tại nơi làm việc của mình, một công cụ tạo mã .hpp từ một mẫu, mẫu là tối thiểu.
Nick

3

Không- không có nội dung trong C ++, cũng như không có bất kỳ cơ sở "kiểm tra miền" nào được tích hợp sẵn.


2

Bạn nghĩ gì về cái này?

#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"
    }
}

Sau đó, bạn có thể sử dụng mã tôi đã đăng ở đây để bật các giá trị.


Bạn vẫn cần thêm vào Apples::insert(4)một nơi nào đó, vì vậy điều này không có lợi thế so với một công tắc.
Sjoerd

1
Anh ấy nói rằng các giá trị đến từ cơ sở dữ liệu, đó là lý do tại sao tôi thêm phương thức "chèn".
Simone

1

Bạn không nên muốn một cái gì đó giống như những gì bạn mô tả tồn tại, tôi sợ rằng có vấn đề trong thiết kế mã của bạn.

Ngoài ra, bạn giả định rằng enum có trong một phạm vi, nhưng không phải lúc nào cũng vậy:

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

Đây không phải là một phạm vi: ngay cả khi có thể, bạn phải kiểm tra mọi số nguyên từ 0 đến 2 ^ n để xem chúng có khớp với giá trị của enum nào đó không?


làm thế nào bạn có thể lấy các giá trị enum từ cơ sở dữ liệu? Các số nguyên được biết đến tại thời điểm biên dịch, vậy tại sao không thể có một chuyển đổi chung dựa trên các mẫu?
Leonid

2
@Leonid: Bởi vì ở một mức độ nào đó, bạn cần phải có một công tắc, như tôi đã nói.
John Dibling

2
@Leonid Templates không phải là viên đạn bạc để giải quyết mọi vấn đề bạn có thể nghĩ ra.
Sjoerd

John đúng. Bạn cần một kiểu phức tạp hơn một enum để làm những gì bạn muốn, tôi nghĩ nó khả thi với hệ thống phân cấp lớp.
Simone

Tôi đã đăng một giải pháp sử dụng hệ thống phân cấp lớp, hãy kiểm tra nó.
Simone

1

Nếu bạn chuẩn bị liệt kê các giá trị enum của mình dưới dạng tham số mẫu, bạn có thể thực hiện việc này trong C ++ 11 với các mẫu khác nhau. Bạn có thể coi đây là một điều tốt, cho phép bạn chấp nhận các tập con của các giá trị enum hợp lệ trong các ngữ cảnh khác nhau; thường hữu ích khi phân tích cú pháp mã từ các nguồn bên ngoài.

Có lẽ không hoàn toàn chung chung như bạn muốn, nhưng bản thân mã kiểm tra đã được tổng quát hóa, bạn chỉ cần chỉ định bộ giá trị. Cách tiếp cận này xử lý các khoảng trống, giá trị tùy ý, v.v.

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);
}

Mặc dù liên kết này có thể trả lời câu hỏi, nhưng tốt hơn hết bạn nên đưa các phần thiết yếu của câu trả lời vào đây và cung cấp liên kết để tham khảo. Các câu trả lời chỉ có liên kết có thể trở nên không hợp lệ nếu trang được liên kết thay đổi. - Từ đánh giá
Tas

1
@Tas Đó là một liên kết đến một câu trả lời SO khác - nó không có các vấn đề giống như một liên kết bên ngoài có. Vẫn cập nhật.
janm

0

C ++ 0x thay thế cho phiên bản "xấu xí", cho phép nhiều enum. Sử dụng danh sách trình khởi tạo thay vì chuyển mạch, IMO sạch hơn một chút. Thật không may, điều này không hoạt động xung quanh nhu cầu mã hóa các giá trị enum.

#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 );
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.