Làm thế nào để tự động chuyển đổi mạnh mẽ gõ enum vào int?


164
#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;
}

Các a::LOCAL_Alà những gì các enum gõ mạnh đang cố gắng để đạt được, nhưng có một sự khác biệt nhỏ: enums bình thường có thể được chuyển đổi thành kiểu số nguyên, trong khi enums gõ mạnh không thể làm điều đó mà không có một dàn diễn viên.

Vì vậy, có cách nào để chuyển đổi một giá trị enum được gõ mạnh thành một kiểu số nguyên mà không cần cast? Nếu có, làm thế nào?

Câu trả lời:


134

Enums đánh máy mạnh mẽ nhằm giải quyết nhiều vấn đề và không chỉ phạm vi vấn đề như bạn đã đề cập trong câu hỏi của bạn:

  1. Cung cấp loại an toàn, do đó loại bỏ chuyển đổi ngầm thành số nguyên bằng cách quảng cáo tích hợp.
  2. Chỉ định các loại cơ bản.
  3. Cung cấp phạm vi mạnh.

Do đó, không thể ngầm chuyển đổi một enum được gõ mạnh thành số nguyên, hoặc thậm chí loại cơ bản của nó - đó là ý tưởng. Vì vậy, bạn phải sử dụng static_castđể thực hiện chuyển đổi rõ ràng.

Nếu vấn đề duy nhất của bạn là phạm vi và bạn thực sự muốn quảng cáo ngầm cho số nguyên, thì tốt hơn hết bạn nên sử dụng enum không được gõ mạnh với phạm vi cấu trúc mà nó được khai báo.


2
Đó là một ví dụ kỳ lạ khác về 'chúng tôi biết rõ hơn những gì bạn muốn làm' từ những người tạo C ++. Các enum thông thường (kiểu cũ) có rất nhiều lợi ích như chuyển đổi ngầm định thành chỉ mục, sử dụng liền mạch các thao tác bitwise, v.v. Các enum kiểu mới đã thêm vào một phạm vi thực sự tuyệt vời, nhưng ... Bạn không thể sử dụng thứ đó (ngay cả với rõ ràng đặc tả kiểu cơ bản!). Vì vậy, bây giờ bạn buộc phải sử dụng các enum kiểu cũ với các thủ thuật như đưa chúng vào struct hoặc tạo các cách giải quyết xấu nhất cho các enum mới như tạo trình bao bọc của riêng bạn xung quanh std :: vector chỉ để khắc phục điều CAST đó. không có bình luận
avtomaton

152

Như những người khác đã nói, bạn không thể có một chuyển đổi ngầm định và đó là thiết kế phụ.

Nếu bạn muốn, bạn có thể tránh sự cần thiết phải chỉ định loại cơ bản trong dàn diễn viên.

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

Một phiên bản C ++ 14 của câu trả lời được cung cấp bởi R. Martinho Fernandes sẽ là:

#include <type_traits>

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

Như với câu trả lời trước, điều này sẽ hoạt động với bất kỳ loại enum và loại cơ bản nào. Tôi đã thêm noexcepttừ khóa vì nó sẽ không bao giờ ném ngoại lệ.


Cập nhật
Điều này cũng xuất hiện trong C ++ hiện đại hiệu quả của Scott Meyers . Xem mục 10 (nó được trình bày chi tiết trong các trang cuối cùng của mục trong bản sao của cuốn sách của tôi).


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
Điều này không làm giảm việc gõ hoặc làm cho mã sạch hơn và có tác dụng phụ làm cho việc tìm kiếm chuyển đổi ngầm định như vậy trong các dự án lớn trở nên khó khăn hơn. Static_cast sẽ dễ dàng tìm kiếm dự án rộng hơn các cấu trúc này.
Atul Kumar

3
@AtulKumar Làm thế nào để tìm kiếm static_cast dễ dàng hơn tìm kiếm to_enum?
Johann Gerell

1
Câu trả lời này cần một số giải thích và tài liệu.
Các cuộc đua nhẹ nhàng trong quỹ đạo

17

Không, không có cách tự nhiên .

Trên thực tế, một trong những động lực đằng sau việc gõ mạnh enum classvào C ++ 11 là ngăn chặn sự chuyển đổi im lặng của họ sang int.


Hãy xem câu trả lời từ Khurshid Normuradov. Nó xuất hiện theo 'cách tự nhiên' và được dự định nhiều hơn trong 'Ngôn ngữ lập trình C ++ (tái bản lần thứ 4)'. Nó không đi theo một cách 'tự động', và đó là điều tốt về nó.
PapaAtHome

@PapaAtHome Tôi không hiểu lợi ích của việc đó qua static_cast. Không có nhiều thay đổi trong việc gõ hoặc mã sạch. Cách tự nhiên ở đây là gì? Một hàm trả về giá trị?
Atul Kumar

1
@ user2876962 Lợi ích đối với tôi là nó không tự động hoặc 'im lặng' như Iammilind nói. Điều đó ngăn chặn lỗi nghiêm trọng để tìm lỗi. Bạn vẫn có thể làm một diễn viên nhưng bạn buộc phải suy nghĩ về nó. Bằng cách đó bạn biết những gì bạn đang làm. Đối với tôi đó là một phần của thói quen 'mã hóa an toàn'. Tôi thích rằng không có chuyển đổi nào không được thực hiện tự động là có khả năng nó có thể gây ra lỗi. Khá nhiều thay đổi trong C ++ 11 liên quan đến hệ thống loại nằm trong danh mục này nếu bạn hỏi tôi.
PapaAtHome

17

Lý do không có chuyển đổi ngầm định (theo thiết kế) đã được đưa ra trong các câu trả lời khác.

Cá nhân tôi sử dụng unary operator+để chuyển đổi từ các lớp enum sang kiểu cơ bản của chúng:

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

Cung cấp khá ít "gõ trên đầu":

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

Trong đó tôi thực sự sử dụng một macro để tạo enum và các hàm toán tử trong một lần chụp.

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

13

Hy vọng điều này sẽ giúp bạn hoặc người khác

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
Điều này được gọi là "loại pucky" và mặc dù được một số trình biên dịch hỗ trợ không khả dụng, vì tiêu chuẩn C ++ nói rằng sau khi bạn đặt un.iđó là "thành viên tích cực" và bạn chỉ có thể đọc thành viên tích cực của liên minh.
Jonathan Wakely

6
@JonathanWakely Bạn đúng về mặt kỹ thuật, nhưng tôi chưa bao giờ thấy một trình biên dịch mà điều này không hoạt động đáng tin cậy. Những thứ như thế này, các hiệp hội ẩn danh và #pragma đã từng là tiêu chuẩn của defacto.
BigSandwich

5
Tại sao sử dụng một cái gì đó mà tiêu chuẩn rõ ràng cấm, khi một diễn viên đơn giản sẽ làm gì? Điều này chỉ sai.
Paul Groke

1
Về mặt kỹ thuật có chính xác hay không, đối với tôi nó dễ đọc hơn các giải pháp khác được tìm thấy ở đây. Và điều quan trọng hơn đối với tôi, nó có thể được sử dụng để giải quyết không chỉ tuần tự hóa, mà còn giải tuần tự hóa lớp enum một cách dễ dàng và định dạng dễ đọc.
Marcin Waśniowski 21/03/17

6
Tôi hoàn toàn tuyệt vọng rằng có những người coi hành vi lộn xộn không xác định này là "cách dễ đọc" hơn là đơn giản static_cast.
gạch dưới

13

Câu trả lời ngắn gọn là bạn không thể như bài viết trên chỉ ra. Nhưng đối với trường hợp của tôi, tôi chỉ đơn giản là không muốn làm lộn xộn không gian tên mà vẫn có các chuyển đổi ngầm định, vì vậy tôi chỉ thực hiện:

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

Kiểu sắp xếp không gian tên thêm một lớp an toàn loại trong khi tôi không phải truyền tĩnh bất kỳ giá trị enum nào cho kiểu bên dưới.


3
Nó không thêm loại an toàn nào (thực sự, bạn vừa loại bỏ loại an toàn) - nó chỉ thêm phạm vi tên.
Các cuộc đua nhẹ nhàng trong quỹ đạo

@LightnessRacesinOrbit vâng tôi đồng ý. Tôi đã nói dối. Về mặt kỹ thuật, chính xác, loại chỉ nằm bên dưới một không gian / phạm vi tên và đủ điều kiện để Foo::Foo. Các thành viên có thể được truy cập dưới dạng Foo::barFoo::bazcó thể được bỏ mặc (và do đó không có nhiều loại an toàn). Có lẽ tốt hơn là hầu như luôn luôn sử dụng các lớp enum đặc biệt là nếu bắt đầu một dự án mới.
solstice333

6

Điều này dường như là không thể với người bản địa enum class, nhưng có lẽ bạn có thể chế giễu enum classmộtclass :

Trong trường hợp này,

enum class b
{
    B1,
    B2
};

sẽ tương đương với:

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

Điều này hầu hết tương đương với bản gốc enum class. Bạn có thể trực tiếp trả về b::B1trong một hàm với kiểu trả về b. Bạn có thể làmswitch case với nó, vv

Và theo tinh thần của ví dụ này, bạn có thể sử dụng các mẫu (có thể cùng với những thứ khác) để khái quát hóa và chế nhạo bất kỳ đối tượng nào có thể được xác định bởi enum classcú pháp.


nhưng B1 và ​​B2 phải được xác định bên ngoài lớp ... hoặc điều này không thể sử dụng cho trường hợp - tiêu đề.h <- class b - main.cpp <---- myvector.push_back (B1)
Fl0

Không phải đó là "constexpr tĩnh b" thay vì "static constexpr int '? Nếu không, b :: B1 chỉ là một int không có sự an toàn nào cả.
Một số Guy

4

Như nhiều người đã nói, không có cách nào để tự động chuyển đổi mà không cần thêm chi phí và quá phức tạp, nhưng bạn có thể giảm việc gõ một chút và làm cho nó trông tốt hơn bằng cách sử dụng lambdas nếu một số diễn viên sẽ được sử dụng một chút trong kịch bản. Điều đó sẽ thêm một chút của cuộc gọi trên chức năng, nhưng sẽ làm cho mã dễ đọc hơn so với các chuỗi static_cast dài như có thể được nhìn thấy bên dưới. Đây có thể không phải là dự án rộng hữu ích, nhưng chỉ rộng lớp.

#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

Ủy ban C ++ đã tiến lên một bước (tách phạm vi ra khỏi không gian tên toàn cầu) và lùi lại năm mươi bước (không phân loại kiểu enum thành số nguyên). Thật đáng buồn,enum class chỉ đơn giản là không thể sử dụng nếu bạn cần giá trị của enum theo bất kỳ cách phi biểu tượng nào.

Giải pháp tốt nhất là hoàn toàn không sử dụng nó, và thay vào đó, hãy tự giới hạn enum bằng cách sử dụng một không gian tên hoặc một cấu trúc. Đối với mục đích này, chúng có thể hoán đổi cho nhau. Bạn sẽ cần phải gõ thêm một chút khi tham chiếu đến loại enum, nhưng điều đó có thể sẽ không thường xuyên.

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
}
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.