วิธีการแปลง enum ที่พิมพ์โดยเฉพาะอย่างยิ่งเป็น 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คือสิ่งที่ enum ที่พิมพ์อย่างรุนแรงพยายามที่จะบรรลุ แต่มีความแตกต่างเล็กน้อย: enums ปกติสามารถแปลงเป็นประเภทจำนวนเต็มได้ในขณะที่ enums ที่พิมพ์อย่างรุนแรงไม่สามารถทำได้โดยไม่ต้องใช้นักแสดง

ดังนั้นมีวิธีการแปลงค่า enum ที่พิมพ์ออกมาอย่างมากเป็นประเภทจำนวนเต็มโดยไม่ต้องใช้แคสต์หรือไม่? ถ้าใช่เป็นอย่างไร

คำตอบ:


134

พิมพ์ enums อย่างยิ่งมุ่งหวังที่จะแก้ปัญหาหลายประการและไม่เพียง แต่ปัญหาการกำหนดขอบเขตตามที่คุณกล่าวถึงในคำถามของคุณ:

  1. จัดเตรียมความปลอดภัยของประเภทจึงกำจัดการแปลงโดยนัยไปเป็นจำนวนเต็มโดยการส่งเสริมการขายที่สำคัญ
  2. ระบุประเภทพื้นฐาน
  3. ให้การกำหนดขอบเขตที่แข็งแกร่ง

ดังนั้นจึงเป็นไปไม่ได้ที่จะแปลงค่า enum ที่พิมพ์อย่างรุนแรงเป็นจำนวนเต็มหรือแม้กระทั่งประเภทพื้นฐาน - นั่นคือแนวคิด ดังนั้นคุณต้องใช้static_castการแปลงอย่างชัดเจน

หากปัญหาเพียงอย่างเดียวของคุณคือการกำหนดขอบเขตและคุณต้องการให้มีการโปรโมตเป็นจำนวนเต็มโดยปริยายคุณควรใช้ enum ที่ไม่พิมพ์อย่างมากโดยมีขอบเขตของโครงสร้างที่ประกาศไว้


2
นั่นเป็นอีกตัวอย่างที่แปลกประหลาดของ 'เรารู้ดีว่าคุณต้องการทำอะไร' จากผู้สร้าง C ++ enums แบบดั้งเดิม (แบบเก่า) มีประโยชน์มากมายเช่นการแปลงเป็นดัชนีโดยปริยาย, ใช้การดำเนินงาน bitwise ได้อย่างราบรื่น ฯลฯ enums รูปแบบใหม่เพิ่มสิ่งที่ดีมากในการกำหนดขอบเขต แต่ ... คุณไม่สามารถใช้สิ่งนั้นได้ ข้อกำหนดประเภทพื้นฐาน!) ดังนั้นตอนนี้คุณถูกบังคับให้ใช้ enums แบบเก่ากับเทคนิคเช่นวางมันลงใน struct หรือสร้างวิธีแก้ปัญหาที่น่าเกลียดที่สุดสำหรับ enums ใหม่เช่นการสร้างเสื้อคลุมของคุณเองรอบ 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

คำตอบรุ่น C ++ 14 ที่จัดทำโดยR. Martinho Fernandesจะเป็น:

#include <type_traits>

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

เช่นเดียวกับคำตอบก่อนหน้านี้จะทำงานกับประเภท enum และพื้นฐานทุกชนิด ฉันได้เพิ่มnoexceptคำหลักแล้วจะไม่มีข้อยกเว้น


การปรับปรุง
นี้ยังปรากฏอยู่ในที่มีประสิทธิภาพสมัยใหม่ 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 การค้นหา static_cast ง่ายกว่าการค้นหา to_enum อย่างไร
Johann Gerell

1
คำตอบนี้ต้องการคำอธิบายและเอกสารประกอบ
Lightness Races ในวงโคจร

17

เลขที่มีไม่มีทางธรรมชาติ

ในความเป็นจริงหนึ่งในแรงจูงใจที่อยู่เบื้องหลังมีพิมพ์มั่นenum classใน C ++ 11 intเพื่อป้องกันไม่ให้มีการแปลงเงียบของพวกเขาไป


ดูที่คำตอบจาก Khurshid Normuradov มันเป็น 'วิธีธรรมชาติ' และมีความตั้งใจมากใน 'ภาษาการเขียนโปรแกรม C ++ (ฉบับที่ 4)' มันไม่ได้มาใน 'ทางอัตโนมัติ' และนั่นก็เป็นเรื่องดี
PapaAtHome

@PapaAtHome ฉันไม่เข้าใจถึงประโยชน์ของมันผ่าน static_cast ความเปลี่ยนแปลงในการพิมพ์หรือความสะอาดโค้ดไม่มากนัก ธรรมชาติเป็นอย่างไรที่นี่? ฟังก์ชันส่งคืนค่าหรือไม่
Atul Kumar

1
@ user2876962 ประโยชน์สำหรับฉันคือมันไม่อัตโนมัติหรือ 'เงียบ' อย่างที่ Iammilind วางไว้ ที่ป้องกันไม่ให้ dificult ค้นหาข้อผิดพลาด คุณยังสามารถแสดงได้ แต่คุณถูกบังคับให้ต้องคิด ด้วยวิธีนี้คุณจะรู้ว่าคุณกำลังทำอะไรอยู่ สำหรับฉันมันเป็นส่วนหนึ่งของนิสัย 'การเข้ารหัสที่ปลอดภัย' ฉันชอบที่ไม่มีการแปลงไม่ได้ทำโดยอัตโนมัติหากมีโอกาสที่จะเกิดข้อผิดพลาดเล็กน้อย การเปลี่ยนแปลงเล็กน้อยใน C ++ 11 ที่เกี่ยวข้องกับระบบประเภทอยู่ในหมวดหมู่นี้ถ้าคุณถามฉัน
PapaAtHome

17

เหตุผลที่ไม่มีการแปลงโดยปริยาย (โดยการออกแบบ) ให้คำตอบอื่น

โดยส่วนตัวฉันใช้ unary operator+สำหรับการแปลงจากคลาส enum เป็นชนิดพื้นฐาน:

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;

ที่จริงฉันใช้มาโครเพื่อสร้าง enums และฟังก์ชั่นโอเปอเรเตอร์ในนัดเดียว

#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
สิ่งนี้เรียกว่า "type punning" และแม้ว่าคอมไพเลอร์บางตัวจะไม่สามารถพกพาได้เนื่องจากมาตรฐาน C ++ บอกว่าหลังจากที่คุณตั้งค่าun.iว่าเป็น "active member" และคุณสามารถอ่านได้เฉพาะสมาชิกที่เป็นสมาชิกของสหภาพ
Jonathan Wakely

6
@ JonathanWakely คุณถูกต้องทางเทคนิค แต่ฉันไม่เคยเห็นคอมไพเลอร์ที่นี่ใช้งานไม่ได้อย่างน่าเชื่อถือ สิ่งต่าง ๆ เช่นนี้สหภาพนิรนามและ #pragma ครั้งเดียวก็คือมาตรฐานของ defacto
BigSandwich

5
เหตุใดจึงใช้สิ่งที่มาตรฐานห้ามอย่างเด็ดขาดเมื่อนักแสดงธรรมดาจะทำอย่างไร นี่เป็นเพียงความผิด
Paul Groke

1
ถูกต้องทางเทคนิคหรือไม่สำหรับฉันมันเป็นวิธีที่อ่านง่ายขึ้นแล้ววิธีแก้ปัญหาอื่น ๆ ที่พบที่นี่ และสิ่งที่สำคัญสำหรับฉันก็คือมันสามารถใช้เพื่อแก้ปัญหาไม่เพียง แต่การทำให้เป็นอนุกรมเท่านั้น
Marcin Waśniowski

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
มันไม่เพิ่มความปลอดภัยประเภทใด ๆ (แน่นอนคุณเพิ่งลบความปลอดภัยประเภท) - มันเพิ่มการกำหนดขอบเขตชื่อเท่านั้น
การแข่งขัน Lightness ใน Orbit

@LightnessRacesinOrbit ใช่ฉันเห็นด้วย ฉันโกหก. เทคนิคที่จะแน่นอนชนิดเป็นเพียงภายใต้ชื่อพื้นที่ / Foo::Fooขอบเขตและมีสิทธิ์ที่จะได้อย่างเต็มที่ สมาชิกสามารถเข้าถึงได้Foo::barและFoo::bazและสามารถใช้งานโดยนัย (และไม่ปลอดภัยมากนัก) น่าจะดีกว่าที่จะใช้คลาส enum โดยเฉพาะอย่างยิ่งถ้าเริ่มโครงการใหม่
solstice333

6

ดูเหมือนว่าจะเป็นไปไม่ได้สำหรับคนพื้นเมืองenum classแต่คุณอาจเยาะเย้ย a 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::B1สำหรับฟังก์ชันที่มีประเภทส่งคืนbโดยตรง คุณสามารถทำได้switch caseกับมัน ฯลฯ

และในจิตวิญญาณของตัวอย่างนี้คุณสามารถใช้แม่แบบ (อาจรวมกับสิ่งอื่น ๆ ) เพื่อพูดคุยและเยาะเย้ยวัตถุที่เป็นไปได้ที่กำหนดโดยenum classไวยากรณ์


แต่ต้องกำหนด B1 และ B2 นอกคลาส ... หรือไม่สามารถใช้กับ case - header.h <- class b - main.cpp <---- myvector.push_back (B1)
Fl0

ไม่ควรที่จะเป็น "constexpr b แบบคงที่" แทนที่จะเป็น "static constexpr int" มิฉะนั้น b :: B1 เป็นเพียง int โดยไม่มีการจัดระดับความปลอดภัยเลย
บาง 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 ++ ใช้เวลาหนึ่งก้าวไปข้างหน้า (กำหนดขอบเขตจากเนมสเปซส่วนกลาง) และถอยกลับห้าสิบขั้นตอน (ไม่มีการสลายประเภท enum เป็นจำนวนเต็ม) น่าเศร้าที่enum classไม่สามารถใช้งานได้ง่ายหากคุณต้องการคุณค่าของ enum ในรูปแบบที่ไม่ใช่สัญลักษณ์

ทางออกที่ดีที่สุดคือไม่ใช้เลยและให้ขอบเขตตัวเองโดยใช้เนมสเปซหรือโครงสร้างแทน สำหรับจุดประสงค์นี้พวกมันสามารถใช้แทนกันได้ คุณจะต้องพิมพ์เพิ่มอีกเล็กน้อยเมื่ออ้างถึงประเภท enum แต่อาจไม่บ่อยนัก

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.