Güçlü yazılan numaralandırmayı otomatik olarak int'e nasıl dönüştürebilirim?


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_AKesinlikle yazılı Çeteleler alçıda olmadan bunu yapamaz iken, normal çeteleler, tamsayı türü dönüştürülebilir: kesinlikle yazılı enum ulaşmak için çalışıyor, ama küçük bir fark vardır budur.

Peki, güçlü bir şekilde türlenmiş bir enum değerini, cast olmadan bir tamsayı türüne dönüştürmenin bir yolu var mı? Evet ise, nasıl?

Yanıtlar:


134

Birden fazla sorunu çözmeyi amaçlayan ve yalnızca sorunuzda belirttiğiniz gibi kapsam belirleme sorununu değil, güçlü bir şekilde yazılmış numaralandırmalar:

  1. Tip güvenliği sağlayın, böylece entegre tanıtım ile tam sayıya örtük dönüşümü ortadan kaldırın.
  2. Temel türleri belirtin.
  3. Güçlü kapsam belirleme sağlayın.

Bu nedenle, kuvvetle yazılan bir sayıyı tamsayılara, hatta altta yatan türüne örtük olarak dönüştürmek imkansızdır - fikir budur. Bu yüzden static_castdönüşümü açık yapmak için kullanmanız gerekir.

Tek probleminiz kapsamayı içeriyorsa ve gerçekten de tamsayılara örtük bir tanıtım yapmak istiyorsanız, bildirildiği yapının kapsamı ile güçlü yazılmamış numaralandırma kullanarak daha iyi durumda olursunuz.


2
Bu, C ++ içerik oluşturucularının 'ne yapmak istediğinizi daha iyi biliyoruz' garip bir örneğidir. Geleneksel (eski tarz) numaralandırmalar, dizinlere örtük dönüştürme, bitsel işlemlerin sorunsuz kullanımı vb. Gibi birçok faydaya sahipti. Yeni stil numaralandırmalar gerçekten harika bir kapsam ekledi, ama ... Sadece bu şeyi kullanamazsınız (açık olsa bile) altta yatan tip özellikleri!). Şimdi ya eski stil numaralarını yapıya koymak gibi hilelerle kullanmak zorundasınız ya da sadece bu CAST şeyinin üstesinden gelmek için std :: vector etrafında kendi paketleyicinizi oluşturmak gibi yeni numaralar için en çirkin geçici çözümler yaratmaya zorlanıyorsunuz. yorum yok
avtomaton

152

Diğerlerinin söylediği gibi, örtük bir dönüşüme sahip olamazsınız ve bu tasarım gereğidir.

İsterseniz, dökümde altta yatan türü belirtme gereksinimini ortadan kaldırabilirsiniz.

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 tarafından verilen cevabın C ++ 14 versiyonu :

#include <type_traits>

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

Önceki cevapta olduğu gibi, bu her türlü numaralandırma ve altta yatan tiple çalışacaktır. noexceptAsla bir istisna atmayacak gibi anahtar kelime ekledim .


Güncelleme
Bu ayrıca Scott Meyers tarafından Effective Modern C ++ ' da da görülmektedir . 10. maddeye bakınız (kitabın kopyasındaki öğenin son sayfalarında ayrıntılı olarak verilmiştir).


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
Bu, yazmayı azaltmaz veya kodu daha temiz hale getirmez ve büyük projelerde bu tür örtülü dönüşümleri bulmayı zorlaştırmanın yan etkilerine sahiptir. Static_cast, projeyi aramak için bu yapılardan daha kolay olurdu.
Atul Kumar

3
@AtulKumar static_cast araması to_enum aramasından daha kolay mı?
Johann Gerell

1
Bu cevabın bazı açıklama ve belgelere ihtiyacı var.
Orbit'te Hafiflik Yarışları

17

Hayır . Doğal bir yol yok .

Aslında, enum classC ++ 11'e güçlü bir şekilde yazmanın ardındaki motivasyonlardan biri, sessiz dönüşümlerini önlemektir int.


Khurshid Normuradov'un cevabına bir göz atın. Bu 'doğal yol' ile gelir ve 'C ++ Programlama Dili (4. baskı)' da amaçlandığı gibidir. 'Otomatik bir şekilde' gelmez ve bu iyi bir şeydir.
Sayfa

Static_cast üzerinden bunun faydasını anlamıyorum. Yazma veya kod temizliğinde çok fazla değişiklik yok. Burada doğal yol nedir? Değer döndüren bir işlev mi?
Atul Kumar

1
@ user2876962 Avantajı, benim için, Iammilind'in söylediği gibi otomatik veya 'sessiz' olmaması. Bu, dificult'un hata bulmasını önler. Hâlâ kadro yapabilirsin, ama bunu düşünmek zorundasın. Bu şekilde ne yaptığınızı bilirsiniz. Bana göre bu 'güvenli kodlama' alışkanlığının bir parçası. Ben hiçbir dönüşüm otomatik yapılmamasını tercih bir hata getirme şansı bir şerit vardır. Bana sorarsanız, C ++ 11 tür sistemiyle ilgili birkaç değişiklik bu kategoriye girer.
Sayfa

17

Örtük dönüşümün (tasarım gereği) olmamasının nedeni diğer cevaplarda verilmiştir.

Ben şahsen operator+enum sınıflarından onların altta yatan tür dönüşüm için unary kullanın :

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

Hangi oldukça az "yazarak yükü" verir:

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

Ben aslında bir makro numaralandırmalar oluşturmak ve operatör tek çekimde çalışır.

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

13

Umarım bu size veya başka birine yardımcı olur

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
Buna "tip punning" denir ve bazı derleyiciler tarafından desteklenmesine rağmen C ++ standardı, ayarladıktan sonra un.i"aktif üye" olduğunu ve yalnızca bir birliğin aktif üyesini okuyabileceğinizi belirttiği için taşınabilir değildir .
Jonathan Wakely

6
@JonathanWakely Teknik olarak haklısınız, ancak bunun güvenilir bir şekilde çalışmadığı bir derleyici görmedim. Bunun gibi şeyler, anonim sendikalar ve #pragma bir defalık standartlarıdır.
BigSandwich

5
Neden basit bir oyuncu kadrosunun yapacağı standartın açıkça yasakladığı bir şey kullanıyorsunuz? Bu sadece yanlış.
Paul Groke

1
Teknik olarak doğru ya da değil, benim için burada bulunan diğer çözümlerden çok daha okunabilir. Ve benim için daha önemli olan, sadece serileştirmeyi değil, aynı zamanda enum sınıfının serileştirilmesini ve okunabilir formatı çözmek için kullanılabilir.
Marcin Waśniowski

6
Ben kesinlikle bu dağınık tanımsız davranışı basit bir şekilde "daha okunabilir" olarak düşünen insanlar olduğunu umutsuzluk static_cast.
underscore_d

13

Kısa cevap, yukarıdaki yayınların işaret ettiği gibi yapamazsınız. Ama benim durumum için, sadece ad alanını karıştırmak istemedim ama yine de örtük dönüşümler var, bu yüzden yaptım:

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

Altta yatan türe herhangi bir enum değeri statik olarak dökmek zorunda değilken, ad türü bir tür güvenlik katmanı ekler.


3
Herhangi bir tür güvenlik eklemez (gerçekten de tür güvenliğini kaldırdınız) - yalnızca ad kapsamı ekler.
Yörüngedeki Hafiflik Yarışları

@LightnessRacesinOrbit evet katılıyorum. Yalan söyledim. Teknik olarak, kesin olarak, tür bir ad alanının / kapsamın hemen altındadır ve tam olarak nitelendirilir Foo::Foo. Üyeler Foo::barve olarak erişilebilir ve Foo::bazdolaylı olarak yayınlanabilir (ve bu nedenle çok fazla tür güvenliği yoktur). Özellikle yeni bir projeye başlarsanız, enum sınıflarını kullanmak her zaman daha iyidir.
solstice333

6

Bu yerli ile imkansız görünüyor enum class, ama muhtemelen bir enum classile aclass :

Bu durumda,

enum class b
{
    B1,
    B2
};

şuna eşdeğer olur:

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

Bu çoğunlukla orijinaline eşdeğerdir enum class. b::B1Dönüş türü olan bir işlevde doğrudan geri dönebilirsiniz b. switch caseBununla vb. Yapabilirsiniz .

Ve bu örneğin ruhuyla, enum classsözdizimi tarafından tanımlanan olası herhangi bir nesneyi genelleştirmek ve alay etmek için şablonları (muhtemelen diğer şeylerle birlikte) kullanabilirsiniz .


ancak B1 ve B2 sınıf dışında tanımlanmalıdır ... ya da bu durum - header.h için kullanılamaz. <- class b - main.cpp <---- myvector.push_back (B1)
Fl0

Bunun yerine "statik constexpr int" yerine "statik constexpr b" olmamalı mı?
Bazı Guy

4

Birçoğunun dediği gibi, genel giderler ve çok fazla karmaşıklık eklemeden otomatik olarak dönüştürmenin bir yolu yoktur, ancak bir senaryoda biraz döküm kullanılacaksa lambdas kullanarak lambda'ları kullanarak daha iyi görünmesini sağlayabilirsiniz. Bu, biraz işlev ek yükü ekler, ancak kodu aşağıda görüldüğü gibi uzun static_cast dizelerine kıyasla daha okunabilir hale getirir. Bu proje çapında yararlı olmayabilir, sadece sınıf çapında olabilir.

#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 ++ komitesi bir adım ileri (küresel ad alanından numaralandırma kapsamı) ve elli adım geri (tamsayıya enum tipi bozulma yok) gitti. Ne yazık ki,enum class enumun değerine sembolik olmayan bir şekilde ihtiyacınız varsa, sadece kullanılamaz.

En iyi çözüm, hiç kullanmamak ve bunun yerine bir ad alanı veya bir yapı kullanarak numaralandırmayı kapsamaktır. Bu amaçla değiştirilebilirler. Enum türünün kendisine atıfta bulunurken biraz fazladan yazmanız gerekir, ancak bu muhtemelen sık olmayacaktır.

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
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.