modern C ++ 11 / C ++ 14 / C ++ 17 ve gelecekteki C ++ 20'de dize sıralaması


354

Tüm diğer benzer soruların aksine, bu soru yeni C ++ özelliklerinin kullanılmasıyla ilgilidir.

Birçok cevabı okuduktan sonra henüz bulamadım:

Misal

Bir örnek genellikle uzun bir açıklamadan daha iyidir.
Bu pasajı Coliru'da derleyebilir ve çalıştırabilirsiniz .
( Eski bir örnek daha mevcuttur)

#include <map>
#include <iostream>

struct MyClass
{
    enum class MyEnum : char {
        AAA = -8,
        BBB = '8',
        CCC = AAA + BBB
    };
};

// Replace magic() by some faster compile-time generated code
// (you're allowed to replace the return type with std::string
// if that's easier for you)
const char* magic (MyClass::MyEnum e)
{
    const std::map<MyClass::MyEnum,const char*> MyEnumStrings {
        { MyClass::MyEnum::AAA, "MyClass::MyEnum::AAA" },
        { MyClass::MyEnum::BBB, "MyClass::MyEnum::BBB" },
        { MyClass::MyEnum::CCC, "MyClass::MyEnum::CCC" }
    };
    auto   it  = MyEnumStrings.find(e);
    return it == MyEnumStrings.end() ? "Out of range" : it->second;
}

int main()
{
   std::cout << magic(MyClass::MyEnum::AAA) <<'\n';
   std::cout << magic(MyClass::MyEnum::BBB) <<'\n';
   std::cout << magic(MyClass::MyEnum::CCC) <<'\n';
}

Kısıtlamalar

  • Lütfen diğer cevapların veya temel bağlantının değersiz bir şekilde çoğaltılmaması gerekir .
  • Lütfen bloat makro tabanlı cevaptan kaçının veya #defineek yükü mümkün olduğunca en aza indirmeye çalışın .
  • Lütfen el kitabı yok enum-> stringharitalama.

Olması güzel

  • enumSıfırdan farklı bir sayıdan başlayan destek değerleri
  • Negatif enumdeğerleri destekleyin
  • Parçalanmış enumdeğerleri destekleyin
  • Destek class enum(C ++ 11)
  • class enum : <type>İzin verilen destek <type>(C ++ 11)
  • Bir dizeye derleme zamanı (çalışma zamanı değil) dönüşümleri
    veya çalışma zamanında en azından hızlı yürütme (ör. std::mapHarika bir fikir değildir ...)
  • constexpr (C ++ 11, daha sonra C ++ 14/17 / 20'de rahatladı)
  • noexcept (C ++ 11)
  • C ++ 17 / C ++ 20 dostu snippet

Olası bir fikir, C ++ derleyici yeteneklerini kullanarak derleme zamanında C ++ kodu oluşturmak variadic template classve constexprişlevlere dayalı meta programlama hileleri kullanarak olabilir ...


4
(belki konu) bu Qt ile ilgili blog'a bakın. woboq.com/blog/reflection-in-cpp-and-qt-moc.html . Qtc'nin (meta-nesne-derleyici) C ++ yansımasını (önerilen standart) kullanarak değiştirme olasılığını açıklar.
ibre5041

10
N4113 :std::enumerator::identifier_v<MyEnum, MyEnum::AAA>
ecatmur

1
Kişisel olarak bu soruyu, değişken makro argümanları arasında geçiş yapmamı ve hepsinin üzerinde bir işlev gerçekleştirmemi sağlayan küçük bir önişlemci yardımcı programı kitaplığı uygulayarak çözdüm. Enum değerlerini makro bağımsız değişkenleri olarak iletiyorum ve otomatik olarak enum ve string dizisini önişlemci üzerinden üretiyorum. Bunu büyük olasılıkla Boost Preprocessor kullanarak yapabilirsiniz.
Vittorio Romeo

2
her şey C ++ ile çözülmeli mi? Dize gösterimi için otomatik olarak kod oluşturmak çok kolaydır, sadece birkaç satır kod.
Karoly Horvath

2
"Mümkünse C makro tabanlı cevaplar vermeyin" iyi, eğer C ++ 17 için beklemek istemiyorsanız kullanılabilir hiçbir şey yoktur ve bakımını yapmak zorunda olmadığınız sürece enums ilan etmek o kadar da kötü değil DEC_ENUM(enumname, (a,b,c,(d,b),(e,42))). makro oluşturma ... ve bu tür vakaları dile yerleştirme, daha güçlü bir şablon / makro melez yerine sadece başka bir tür hack'tir. Makroların artık işe yaramadığını söyleyebilmek için, makroların tüm bu kullanışlı kullanımlarını dile eklememeliyiz.
PlazmaHH

Yanıtlar:


43

Sihirli Numaralandırma yalnızca başlık kitaplığı, C ++ 17 için numaralandırmalar (dize, dize, yineleme) için statik yansıma sağlar.

#include <magic_enum.hpp>

enum Color { RED = 2, BLUE = 4, GREEN = 8 };

Color color = Color::RED;
auto color_name = magic_enum::enum_name(color);
// color_name -> "RED"

std::string color_name{"GREEN"};
auto color = magic_enum::enum_cast<Color>(color_name)
if (color.has_value()) {
  // color.value() -> Color::GREEN
};

Daha fazla örnek için ev havuzunu kontrol edin https://github.com/Neargye/magic_enum .

Dezavantajı nerede?

Bu kütüphane derleyiciye özgü bir kesmek kullanır ( __PRETTY_FUNCTION__/__FUNCSIG__ , Clang> = 5, MSVC> = 15.3 ve GCC> = 9 üzerinde çalışan ) .

Enum değeri aralıkta olmalıdır [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX].

  • Varsayılan olarak MAGIC_ENUM_RANGE_MIN = -128,MAGIC_ENUM_RANGE_MAX = 128 ,.

  • Varsayılan olarak tüm numaralandırma türleri için başka bir aralığa ihtiyaç duyarsanız, makroyu MAGIC_ENUM_RANGE_MINve öğesini yeniden tanımlayın MAGIC_ENUM_RANGE_MAX.

  • MAGIC_ENUM_RANGE_MINküçük veya eşit 0olmalı ve büyük olmalıdır INT16_MIN.

  • MAGIC_ENUM_RANGE_MAXdaha büyük olmalı ve daha küçük 0olmalıdırINT16_MAX .

  • Belirli numaralandırma türü için başka bir aralığa ihtiyacınız varsa, gerekli numaralandırma türü için enum_range uzmanlığı ekleyin.

    #include <magic_enum.hpp>
    
    enum number { one = 100, two = 200, three = 300 };
    
    namespace magic_enum {
    template <>
      struct enum_range<number> {
        static constexpr int min = 100;
        static constexpr int max = 300;
    };
    }

Neden menzil sınırları? Bir tür özyineleme derinliğini sınırlamak mı, yoksa bir tür derleme zamanı doğrusal arama nedeniyle mi?
Emile Cormier

Bu harika. Teşekkür ederim! Derleyici sadece bir kez constexpr std :: dizisini değerlendirecek kadar akıllıysa bile etkilidir. Çok çok güzel.
iestyn

87

( Better_enums kütüphanesinin yaklaşımı )

Geçerli C ++ dize için numaralandırma dize yapmanın bir yolu vardır:

ENUM(Channel, char, Red = 1, Green, Blue)

// "Same as":
// enum class Channel : char { Red = 1, Green, Blue };

Kullanımı:

Channel     c = Channel::_from_string("Green");  // Channel::Green (2)
c._to_string();                                  // string "Green"

for (Channel c : Channel::_values())
    std::cout << c << std::endl;

// And so on...

Tüm işlemler yapılabilir constexpr. @Ecatmur tarafından verilen cevapta belirtilen C ++ 17 yansıma teklifini de uygulayabilirsiniz.

  • Sadece bir makro var. Bunun mümkün olan en düşük düzeyde olduğuna inanıyorum, çünkü önişlemci dizgisi (# ) geçerli C ++ dize bir dizgeye dönüştürmek için tek yol .
  • Makro oldukça mütevazi - başlatıcılar da dahil olmak üzere sabit bildirimler yerleşik bir numaralandırma bildirimine yapıştırılır. Bu, yerleşik bir numarada olduğu gibi aynı sözdizimine ve anlama sahip oldukları anlamına gelir.
  • Tekrarlama ortadan kaldırılmıştır.
  • Uygulama, en doğal ve en az C ++ 11 nedeniyle, yararlıdır constexpr. C ++ 98 + ile çalışmak için de yapılabilir __VA_ARGS__. Kesinlikle modern C ++.

Makronun tanımı biraz dahil, bu yüzden bunu birkaç şekilde cevaplıyorum.

  • Bu cevabın büyük bir kısmı StackOverflow alan kısıtlamaları için uygun olduğunu düşünüyorum bir uygulamadır.
  • Uzun biçimli bir öğreticide uygulamanın temellerini açıklayan bir CodeProject makalesi de vardır . [ Buraya taşınmalı mıyım? Bence SO cevabı için çok fazla ].
  • Bir yoktur tam özellikli kütüphane "Daha İyi Numaralamalar" bunu uygulayan tek başlık dosyasına makro. Ayrıca , C ++ 17 yansıma önerisi N4113'ün geçerli revizyonu olan N4428 Tip Özellik Sorguları uygular. Yani, en azından bu makro aracılığıyla bildirilen numaralandırmalar için, C ++ 11 / C ++ 14'te önerilen C ++ 17 enum yansımasına sahip olabilirsiniz.

Bu cevabı kütüphanenin özelliklerine genişletmek kolaydır - burada "önemli" hiçbir şey kalmaz. Bununla birlikte, oldukça sıkıcıdır ve derleyici taşınabilirliği endişeleri vardır.

Feragatname : Hem CodeProject makalesinin hem de kütüphanenin yazarıyım.

Bu cevapta , kütüphanede ve N4428'in Wandbox'ta çevrimiçi canlı olarak uygulanmasında kodu deneyebilirsiniz . Kütüphane dokümantasyonunda ayrıca, bu teklifin numaralandırmalar bölümünü açıklayan N4428 olarak nasıl kullanılacağına dair bir genel bakış da bulunmaktadır .


açıklama

Aşağıdaki kod, numaralandırmalar ve dizeler arasındaki dönüşümleri uygular. Ancak, yineleme gibi başka şeyler de yapılabilir. Bu cevap bir enum a struct. Bunun structyerine numaralandırmanın yanında da özellikler oluşturabilirsiniz .

Strateji böyle bir şey üretmektir:

struct Channel {
    enum _enum : char { __VA_ARGS__ };
    constexpr static const Channel          _values[] = { __VA_ARGS__ };
    constexpr static const char * const     _names[] = { #__VA_ARGS__ };

    static const char* _to_string(Channel v) { /* easy */ }
    constexpr static Channel _from_string(const char *s) { /* easy */ }
};

Sorunlar:

  1. {Red = 1, Green, Blue}Değerler dizisinin başlatıcısı gibi bir şeyle sonuçlanacağız . RedAtanabilir bir ifade olmadığından bu geçerli C ++ değildir. Bu, her sabiti Tbir atama işleci olan bir türe atayarak çözülür , ancak atamayı bırakır:{(T)Red = 1, (T)Green, (T)Blue} .
  2. Benzer şekilde, {"Red = 1", "Green", "Blue"}names dizisi için başlatıcı olarak sonuçlanacağız . Kesmemiz gerekecek " = 1". Bunu derleme zamanında yapmanın harika bir yolunun farkında değilim, bu yüzden bunu çalışma süresine erteleyeceğiz. Sonuç olarak, _to_stringolmayacak constexpr, ama_from_string yine de olabilir constexpr, çünkü boşluk olmadan ve eşit olmayan işaretlere sonlandırılmamış dizelerle karşılaştırıldığında sonlandırıcı olarak eşit olabiliriz.
  3. Yukarıdakilerin her ikisinde de her öğeye başka bir makro uygulayabilen bir "eşleme" makrosu gerekir __VA_ARGS__ . Bu oldukça standart. Bu cevap 8 adede kadar elemanı işleyebilen basit bir versiyon içermektedir.
  4. Makro gerçekten bağımsız olacaksa, ayrı bir tanım gerektiren statik veri bildirmemesi gerekir. Uygulamada, bu, dizilerin özel işleme ihtiyacı olduğu anlamına gelir. İki olası çözüm vardır: constexpr(veya sadece const) ad alanı kapsamındaki diziler veya constexprstatik olmayan satır içi işlevlerdeki normal diziler . Bu yanıttaki kod C ++ 11 içindir ve önceki yaklaşımı kullanır. CodeProject makalesi C ++ 98 içindir ve ikincisini alır.

kod

#include <cstddef>      // For size_t.
#include <cstring>      // For strcspn, strncpy.
#include <stdexcept>    // For runtime_error.



// A "typical" mapping macro. MAP(macro, a, b, c, ...) expands to
// macro(a) macro(b) macro(c) ...
// The helper macro COUNT(a, b, c, ...) expands to the number of
// arguments, and IDENTITY(x) is needed to control the order of
// expansion of __VA_ARGS__ on Visual C++ compilers.
#define MAP(macro, ...) \
    IDENTITY( \
        APPLY(CHOOSE_MAP_START, COUNT(__VA_ARGS__)) \
            (macro, __VA_ARGS__))

#define CHOOSE_MAP_START(count) MAP ## count

#define APPLY(macro, ...) IDENTITY(macro(__VA_ARGS__))

#define IDENTITY(x) x

#define MAP1(m, x)      m(x)
#define MAP2(m, x, ...) m(x) IDENTITY(MAP1(m, __VA_ARGS__))
#define MAP3(m, x, ...) m(x) IDENTITY(MAP2(m, __VA_ARGS__))
#define MAP4(m, x, ...) m(x) IDENTITY(MAP3(m, __VA_ARGS__))
#define MAP5(m, x, ...) m(x) IDENTITY(MAP4(m, __VA_ARGS__))
#define MAP6(m, x, ...) m(x) IDENTITY(MAP5(m, __VA_ARGS__))
#define MAP7(m, x, ...) m(x) IDENTITY(MAP6(m, __VA_ARGS__))
#define MAP8(m, x, ...) m(x) IDENTITY(MAP7(m, __VA_ARGS__))

#define EVALUATE_COUNT(_1, _2, _3, _4, _5, _6, _7, _8, count, ...) \
    count

#define COUNT(...) \
    IDENTITY(EVALUATE_COUNT(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1))



// The type "T" mentioned above that drops assignment operations.
template <typename U>
struct ignore_assign {
    constexpr explicit ignore_assign(U value) : _value(value) { }
    constexpr operator U() const { return _value; }

    constexpr const ignore_assign& operator =(int dummy) const
        { return *this; }

    U   _value;
};



// Prepends "(ignore_assign<_underlying>)" to each argument.
#define IGNORE_ASSIGN_SINGLE(e) (ignore_assign<_underlying>)e,
#define IGNORE_ASSIGN(...) \
    IDENTITY(MAP(IGNORE_ASSIGN_SINGLE, __VA_ARGS__))

// Stringizes each argument.
#define STRINGIZE_SINGLE(e) #e,
#define STRINGIZE(...) IDENTITY(MAP(STRINGIZE_SINGLE, __VA_ARGS__))



// Some helpers needed for _from_string.
constexpr const char    terminators[] = " =\t\r\n";

// The size of terminators includes the implicit '\0'.
constexpr bool is_terminator(char c, size_t index = 0)
{
    return
        index >= sizeof(terminators) ? false :
        c == terminators[index] ? true :
        is_terminator(c, index + 1);
}

constexpr bool matches_untrimmed(const char *untrimmed, const char *s,
                                 size_t index = 0)
{
    return
        is_terminator(untrimmed[index]) ? s[index] == '\0' :
        s[index] != untrimmed[index] ? false :
        matches_untrimmed(untrimmed, s, index + 1);
}



// The macro proper.
//
// There are several "simplifications" in this implementation, for the
// sake of brevity. First, we have only one viable option for declaring
// constexpr arrays: at namespace scope. This probably should be done
// two namespaces deep: one namespace that is likely to be unique for
// our little enum "library", then inside it a namespace whose name is
// based on the name of the enum to avoid collisions with other enums.
// I am using only one level of nesting.
//
// Declaring constexpr arrays inside the struct is not viable because
// they will need out-of-line definitions, which will result in
// duplicate symbols when linking. This can be solved with weak
// symbols, but that is compiler- and system-specific. It is not
// possible to declare constexpr arrays as static variables in
// constexpr functions due to the restrictions on such functions.
//
// Note that this prevents the use of this macro anywhere except at
// namespace scope. Ironically, the C++98 version of this, which can
// declare static arrays inside static member functions, is actually
// more flexible in this regard. It is shown in the CodeProject
// article.
//
// Second, for compilation performance reasons, it is best to separate
// the macro into a "parametric" portion, and the portion that depends
// on knowing __VA_ARGS__, and factor the former out into a template.
//
// Third, this code uses a default parameter in _from_string that may
// be better not exposed in the public interface.

#define ENUM(EnumName, Underlying, ...)                               \
namespace data_ ## EnumName {                                         \
    using _underlying = Underlying;                                   \
    enum { __VA_ARGS__ };                                             \
                                                                      \
    constexpr const size_t           _size =                          \
        IDENTITY(COUNT(__VA_ARGS__));                                 \
                                                                      \
    constexpr const _underlying      _values[] =                      \
        { IDENTITY(IGNORE_ASSIGN(__VA_ARGS__)) };                     \
                                                                      \
    constexpr const char * const     _raw_names[] =                   \
        { IDENTITY(STRINGIZE(__VA_ARGS__)) };                         \
}                                                                     \
                                                                      \
struct EnumName {                                                     \
    using _underlying = Underlying;                                   \
    enum _enum : _underlying { __VA_ARGS__ };                         \
                                                                      \
    const char * _to_string() const                                   \
    {                                                                 \
        for (size_t index = 0; index < data_ ## EnumName::_size;      \
             ++index) {                                               \
                                                                      \
            if (data_ ## EnumName::_values[index] == _value)          \
                return _trimmed_names()[index];                       \
        }                                                             \
                                                                      \
        throw std::runtime_error("invalid value");                    \
    }                                                                 \
                                                                      \
    constexpr static EnumName _from_string(const char *s,             \
                                           size_t index = 0)          \
    {                                                                 \
        return                                                        \
            index >= data_ ## EnumName::_size ?                       \
                    throw std::runtime_error("invalid identifier") :  \
            matches_untrimmed(                                        \
                data_ ## EnumName::_raw_names[index], s) ?            \
                    (EnumName)(_enum)data_ ## EnumName::_values[      \
                                                            index] :  \
            _from_string(s, index + 1);                               \
    }                                                                 \
                                                                      \
    EnumName() = delete;                                              \
    constexpr EnumName(_enum value) : _value(value) { }               \
    constexpr operator _enum() const { return (_enum)_value; }        \
                                                                      \
  private:                                                            \
    _underlying     _value;                                           \
                                                                      \
    static const char * const * _trimmed_names()                      \
    {                                                                 \
        static char     *the_names[data_ ## EnumName::_size];         \
        static bool     initialized = false;                          \
                                                                      \
        if (!initialized) {                                           \
            for (size_t index = 0; index < data_ ## EnumName::_size;  \
                 ++index) {                                           \
                                                                      \
                size_t  length =                                      \
                    std::strcspn(data_ ## EnumName::_raw_names[index],\
                                 terminators);                        \
                                                                      \
                the_names[index] = new char[length + 1];              \
                                                                      \
                std::strncpy(the_names[index],                        \
                             data_ ## EnumName::_raw_names[index],    \
                             length);                                 \
                the_names[index][length] = '\0';                      \
            }                                                         \
                                                                      \
            initialized = true;                                       \
        }                                                             \
                                                                      \
        return the_names;                                             \
    }                                                                 \
};

ve

// The code above was a "header file". This is a program that uses it.
#include <iostream>
#include "the_file_above.h"

ENUM(Channel, char, Red = 1, Green, Blue)

constexpr Channel   channel = Channel::_from_string("Red");

int main()
{
    std::cout << channel._to_string() << std::endl;

    switch (channel) {
        case Channel::Red:   return 0;
        case Channel::Green: return 1;
        case Channel::Blue:  return 2;
    }
}

static_assert(sizeof(Channel) == sizeof(char), "");

Yukarıdaki program Redbeklediğiniz gibi yazdırılır . Başlatmadan bir enum oluşturamadığınız ve vakalardan birini irade'den sildiğiniz için bir switchderleyici güvenliği vardır (derleyicinize ve bayraklarınıza bağlı olarak). Ayrıca, "Red"derleme sırasında bir numaraya dönüştürüldü unutmayın .


Heya @mrhthepie, düzenlemenizin reddedildiği için üzgünüm. Bu konuyla ilgili e-postayı yeni gördüm. Cevaba ekleyeceğim - bugfix için teşekkürler!
antron

bu harika. Bir parça numaralandırma istersem bu da işe yarar mı? Bir BitFlags enumunu istiyorum gibi, her biri bir 1Umiktar kaydırılıyor mu?
user3240688

1
burada _trimmed_names()yayınladığınız kodda bir bellek sızıntısı var gibi görünüyor ( new char[length + 1]ancak initializedtrue olarak ayarlamıyorsunuz ). bir şey mi kaçırıyorum? aynı sorunu github kodunda görmüyorum.
user3240688

1
Şuna ayarlanır true, ancak ifdalın dışında (başlangıçta mrhthepie tarafından yakalanan bellek sızıntısı). İçeriye taşımalısın ... Düzenleme. Hem buna hem de GH koduna yakından baktığınız için teşekkürler.
antron

1
to_stringBir geri dönebilirler string_viewboş sonlandırma gerektirmez C ++ 17 dan ve constexpr olur.
Yakk - Adam Nevraumont

74

For C ++ 17 C ++ 20, siz Yansıma Çalışma Grubu (SG7) çalışmalarına ilgi duyan edilecektir. Kapsayan kağıtları paralel serisi vardır ifadeleri ( P0194 ) ve gerekçesini, tasarım ve evrimi ( P0385 ). (Bağlantılar her serideki en son makaleye çözümlenir.)

P0194r2 (2016-10-15) itibarıyla sözdizimi önerilen reflexpranahtar kelimeyi kullanır:

meta::get_base_name_v<
  meta::get_element_m<
    meta::get_enumerators_m<reflexpr(MyEnum)>,
    0>
  >

Örneğin ( Matus Choclik'in reflekspr clang kolundan uyarlanmıştır ):

#include <reflexpr>
#include <iostream>

enum MyEnum { AAA = 1, BBB, CCC = 99 };

int main()
{
  auto name_of_MyEnum_0 = 
    std::meta::get_base_name_v<
      std::meta::get_element_m<
        std::meta::get_enumerators_m<reflexpr(MyEnum)>,
        0>
    >;

  // prints "AAA"
  std::cout << name_of_MyEnum_0 << std::endl;
}

Statik yansıma onu C ++ 17'ye dönüştüremedi (daha doğrusu, Issaquah'ta Kasım 2016 standart toplantısında sunulan muhtemelen nihai taslakta), ancak C ++ 20'ye dönüşeceğine dair güven var; dan Herb Sutter'ın gezi raporu :

Özellikle, Yansıma çalışma grubu en son birleştirilmiş statik yansıma teklifini gözden geçirdi ve bir TS veya bir sonraki standart için birleşik statik yansıma teklifini düşünmeye başlamak için bir sonraki toplantımızda ana Evrim gruplarına girmeye hazır buldu.


2
@antron üzgünüm düzenlemeniz reddedildi; Eğer zamanında görseydim onaylardım. N4428'i görmemiştim, bu yüzden kafalarını bıraktığın için teşekkürler.
ecatmur

3
Sorun değil, dahil ettiğiniz için teşekkürler. Neden reddedildiğini merak ediyorum. "Daha doğru yapmıyor" kazan nedenini görüyorum, ancak bugün için açıkça daha doğru.
antron

1
Teşekkürler :-) Yatay kaydırma çubuğundan kaçınmak için son örneği böldüm. Ne yazık ki değeri MyEnum::AAAikinci argüman olarak geçilemez std::meta::get_enumerators_m: - /
olibre

1
Kavramsal olarak basit bir görevin 3 düzey iç içe şablon argümanı gerektirmesi çok fazla mühendislikten geçirilmiştir. Eminim bunun spesifik, teknik nedenleri vardır. Ancak bu, nihai sonucun kullanıcı dostu olduğu anlamına gelmez. C ++ seviyorum ve kod bana mantıklı. Ama günlük olarak çalıştığım diğer programcıların% 90'ı böyle bir kod yüzünden C ++ 'dan kaçıyor. Daha basit ve yerleşik çözümler görmekten hayal kırıklığına uğradım.
void.pointer

2
Yaklaşan Reflection TS standardına dahil edilmek için mevcut tahmin C ++ 23 : otlarutter.com/2018/04/02/…
Tim Rae

25

Bu Yuri Finkelstein'a benzer; ancak destek gerektirmez. Bir harita kullanıyorum, böylece numaralara, herhangi bir sıraya herhangi bir değer atayabilirsiniz.

Enum sınıfının beyanı:

DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);

Aşağıdaki kod otomatik olarak enum sınıfını ve aşırı yüklenmeyi oluşturur:

  • '+' '+ =' std :: string için
  • Akışlar için '<<'
  • sadece dizeye dönüştürmek için '~' (Herhangi bir tekli operatör yapacak, ama kişisel olarak netlik için sevmiyorum)
  • Numaralandırma sayısını almak için '*'

Güçlendirme gerekmez, gerekli tüm fonksiyonlar sağlanır.

Kod:

#include <algorithm>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>

#define STRING_REMOVE_CHAR(str, ch) str.erase(std::remove(str.begin(), str.end(), ch), str.end())

std::vector<std::string> splitString(std::string str, char sep = ',') {
    std::vector<std::string> vecString;
    std::string item;

    std::stringstream stringStream(str);

    while (std::getline(stringStream, item, sep))
    {
        vecString.push_back(item);
    }

    return vecString;
}

#define DECLARE_ENUM_WITH_TYPE(E, T, ...)                                                                     \
    enum class E : T                                                                                          \
    {                                                                                                         \
        __VA_ARGS__                                                                                           \
    };                                                                                                        \
    std::map<T, std::string> E##MapName(generateEnumMap<T>(#__VA_ARGS__));                                    \
    std::ostream &operator<<(std::ostream &os, E enumTmp)                                                     \
    {                                                                                                         \
        os << E##MapName[static_cast<T>(enumTmp)];                                                            \
        return os;                                                                                            \
    }                                                                                                         \
    size_t operator*(E enumTmp) { (void) enumTmp; return E##MapName.size(); }                                 \
    std::string operator~(E enumTmp) { return E##MapName[static_cast<T>(enumTmp)]; }                          \
    std::string operator+(std::string &&str, E enumTmp) { return str + E##MapName[static_cast<T>(enumTmp)]; } \
    std::string operator+(E enumTmp, std::string &&str) { return E##MapName[static_cast<T>(enumTmp)] + str; } \
    std::string &operator+=(std::string &str, E enumTmp)                                                      \
    {                                                                                                         \
        str += E##MapName[static_cast<T>(enumTmp)];                                                           \
        return str;                                                                                           \
    }                                                                                                         \
    E operator++(E &enumTmp)                                                                                  \
    {                                                                                                         \
        auto iter = E##MapName.find(static_cast<T>(enumTmp));                                                 \
        if (iter == E##MapName.end() || std::next(iter) == E##MapName.end())                                  \
            iter = E##MapName.begin();                                                                        \
        else                                                                                                  \
        {                                                                                                     \
            ++iter;                                                                                           \
        }                                                                                                     \
        enumTmp = static_cast<E>(iter->first);                                                                \
        return enumTmp;                                                                                       \
    }                                                                                                         \
    bool valid##E(T value) { return (E##MapName.find(value) != E##MapName.end()); }

#define DECLARE_ENUM(E, ...) DECLARE_ENUM_WITH_TYPE(E, int32_t, __VA_ARGS__)
template <typename T>
std::map<T, std::string> generateEnumMap(std::string strMap)
{
    STRING_REMOVE_CHAR(strMap, ' ');
    STRING_REMOVE_CHAR(strMap, '(');

    std::vector<std::string> enumTokens(splitString(strMap));
    std::map<T, std::string> retMap;
    T inxMap;

    inxMap = 0;
    for (auto iter = enumTokens.begin(); iter != enumTokens.end(); ++iter)
    {
        // Token: [EnumName | EnumName=EnumValue]
        std::string enumName;
        T enumValue;
        if (iter->find('=') == std::string::npos)
        {
            enumName = *iter;
        }
        else
        {
            std::vector<std::string> enumNameValue(splitString(*iter, '='));
            enumName = enumNameValue[0];
            //inxMap = static_cast<T>(enumNameValue[1]);
            if (std::is_unsigned<T>::value)
            {
                inxMap = static_cast<T>(std::stoull(enumNameValue[1], 0, 0));
            }
            else
            {
                inxMap = static_cast<T>(std::stoll(enumNameValue[1], 0, 0));
            }
        }
        retMap[inxMap++] = enumName;
    }

    return retMap;
}

Misal:

DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);

int main(void) {
    TestEnumClass first, second;
    first = TestEnumClass::FOUR;
    second = TestEnumClass::TWO;

    std::cout << first << "(" << static_cast<uint32_t>(first) << ")" << std::endl; // FOUR(4)

    std::string strOne;
    strOne = ~first;
    std::cout << strOne << std::endl; // FOUR

    std::string strTwo;
    strTwo = ("Enum-" + second) + (TestEnumClass::THREE + "-test");
    std::cout << strTwo << std::endl; // Enum-TWOTHREE-test

    std::string strThree("TestEnumClass: ");
    strThree += second;
    std::cout << strThree << std::endl; // TestEnumClass: TWO
    std::cout << "Enum count=" << *first << std::endl;
}

You can run the code here


1
Bu makro tanımında satır kesmeleri olabilir mi?
einpoklum

1
*Enums sayısını almak için aşırı yük ekledim ... Umarım umursamıyorum :-)
Peter VARGA

1
Bu uygulamanın (O (1) dizinleme) std::mapyerine (O (log (n)) dizinleme) kullanmasının herhangi bir nedeni var mı std::unordered_map?
River Tam

1
Ayrıca, ben inlinebağlayıcı gibi "birden fazla tanım" hataları almadan normal gibi başlık dosyalarında enums ilan edilebilir yöntemleri işaretlenmesi gerektiğini düşünüyorum . (aslında bunun en temiz / en iyi çözüm olup olmadığından emin değilim)
River Tam

1
(spam için özür dilerim ama bugün yorumları düzenleyemiyorum) bunun bir başlık dosyasında olmasıyla ilgili başka sorunlar var. Map ( E##MapName), numaralandırmaya da erişimi olan bir derleme birimine taşınmalıdır. Bir çözüm oluşturdum, ancak çok temiz değil ve bunu paylaşmak için izin almam gerekiyor. Şimdilik, bir başlık dosyasında kullanımı desteklemek için gerekli ek özellikler olmadan yöntemleri satır içinde işaretlemenin bir anlamı olmadığını söylemek için yorum yapıyorum.
Tam Nehir

20

2011 yılında makro tabanlı bir çözümü ince ayarlayarak hafta sonu geçirdim ve hiç kullanmadım.

Şu anki prosedürüm Vim'i başlatmak, boş bir anahtar gövdesindeki numaralandırıcıları kopyalamak, yeni bir makro başlatmak, ilk numaralandırıcıyı bir vaka ifadesine dönüştürmek , imleci bir sonraki satırın başına taşımak , makroyu durdurmak ve kalan durumu oluşturmaktır. diğer numaralandırıcılar üzerinde makro çalıştırarak ifadeler.

Vim makroları C ++ makrolarından daha eğlencelidir.

Gerçek hayat örneği:

enum class EtherType : uint16_t
{
    ARP   = 0x0806,
    IPv4  = 0x0800,
    VLAN  = 0x8100,
    IPv6  = 0x86DD
};

Bunu yaratacağım:

std::ostream& operator<< (std::ostream& os, EtherType ethertype)
{
    switch (ethertype)
    {
        case EtherType::ARP : return os << "ARP" ;
        case EtherType::IPv4: return os << "IPv4";
        case EtherType::VLAN: return os << "VLAN";
        case EtherType::IPv6: return os << "IPv6";
        // omit default case to trigger compiler warning for missing cases
    };
    return os << static_cast<std::uint16_t>(ethertype);
}

Ve işte böyle geçiyorum.

Enum dizgelemesi için yerel destek çok daha iyi olurdu. C ++ 17'de yansıma çalışma grubunun sonuçlarını görmekle çok ilgileniyorum.

Bunu yapmanın alternatif bir yolu da yorumlarda @sehe tarafından yayınlanmıştır .


1
Ben de aynen bunu yapıyorum. Her ne kadar genellikle Surround vim ve blok seçimlerini yol boyunca
kullansam da

@sehe İlginç. Şu anda birçok tuş vuruşları için yol gerektirir çünkü "surround" bir göz gerekir.
StackedCrooked

İşte tam kanlı, makro yok ( sayılmadıkça. ): i.imgur.com/gY4ZhBE.gif
sehe

1
Animasyonlu gif sevimli, ancak ne zaman başladığını ve bittiğini ve ne kadar uzakta olduğumuzu söylemek zor. ... aslında çizin, şirin değil, dikkat dağıtıcı. Öldür diyorum.
einpoklum

Vim'deki bu blok seçim yaklaşımı güzel ve hepsi, ama neden sadece bir şey kullanmıyorsunuz :'<,'>s/ *\(.*\)=.*/case EtherType::\1: return os << "\1";/?
Ruslan

14

Bunu beğenip beğenmeyeceğinizi bilmiyorum, bu çözümden pek memnun değilim ama şablon değişkenleri kullanıyor ve şablon uzmanlığını kötüye kullandığından C ++ 14 dostu bir yaklaşımdır:

enum class MyEnum : std::uint_fast8_t {
   AAA,
   BBB,
   CCC,
};

template<MyEnum> const char MyEnumName[] = "Invalid MyEnum value";
template<> const char MyEnumName<MyEnum::AAA>[] = "AAA";
template<> const char MyEnumName<MyEnum::BBB>[] = "BBB";
template<> const char MyEnumName<MyEnum::CCC>[] = "CCC";

int main()
{
    // Prints "AAA"
    std::cout << MyEnumName<MyEnum::AAA> << '\n';
    // Prints "Invalid MyEnum value"
    std::cout << MyEnumName<static_cast<MyEnum>(0x12345678)> << '\n';
    // Well... in fact it prints "Invalid MyEnum value" for any value
    // different of MyEnum::AAA, MyEnum::BBB or MyEnum::CCC.

    return 0;
}

Bu yaklaşımla ilgili en kötüsü, sürdürülmesi gereken bir acıdır, ancak diğer benzer yaklaşımların sürdürülmesi de bir acıdır, değil mi?

Bu yaklaşım hakkında iyi noktalar:

  • Değişken tempates kullanma (C ++ 14 özelliği)
  • Şablon uzmanlığında, geçersiz bir değer kullanıldığında "algılayabilir" (ancak bunun yararlı olup olmadığından emin değilim).
  • Düzgün görünüyor.
  • İsim araması derleme zamanında yapılır.

Live example

Düzenle

Misterious user673679 haklısınız; C ++ 14 değişken şablon yaklaşımı çalışma zamanı durumunu işlemez, unutmak benim hatamdı :(

Ama biz hala bazı modern C ++ özellikleri ve değişken şablon artı değişken bir şablon hile enum değeri dizeden bir çalışma süresi çeviri elde etmek için kullanabilirsiniz ... diğerleri gibi rahatsız edici ama yine de söz değer.

Bir numaradan dizeye haritaya erişimi kısaltmak için bir şablon takma adı kullanmaya başlayalım:

// enum_map contains pairs of enum value and value string for each enum
// this shortcut allows us to use enum_map<whatever>.
template <typename ENUM>
using enum_map = std::map<ENUM, const std::string>;

// This variable template will create a map for each enum type which is
// instantiated with.
template <typename ENUM>
enum_map<ENUM> enum_values{};

Sonra, varyasyon şablonu hilesi:

template <typename ENUM>
void initialize() {}

template <typename ENUM, typename ... args>
void initialize(const ENUM value, const char *name, args ... tail)
{
    enum_values<ENUM>.emplace(value, name);
    initialize<ENUM>(tail ...);
}

Buradaki " en iyi numara ", her bir numaralandırma girişinin değerlerini ve adlarını içeren harita için değişken şablonun kullanılmasıdır; bu harita her çeviri biriminde aynı olacak ve her yerde aynı isme sahip olacak, bu nedenle initializeişlevi şöyle çağırırsak oldukça basit ve temiz olur :

initialize
(
    MyEnum::AAA, "AAA",
    MyEnum::BBB, "BBB",
    MyEnum::CCC, "CCC"
);

Her bir MyEnumgirişe ad veriyoruz ve çalışma zamanında kullanılabilir:

std::cout << enum_values<MyEnum>[MyEnum::AAA] << '\n';

Ancak SFINAE ve aşırı yükleme <<operatörü ile geliştirilebilir :

template<typename ENUM, class = typename std::enable_if<std::is_enum<ENUM>::value>::type>
std::ostream &operator <<(std::ostream &o, const ENUM value)
{
    static const std::string Unknown{std::string{typeid(ENUM).name()} + " unknown value"};
    auto found = enum_values<ENUM>.find(value);

    return o << (found == enum_values<ENUM>.end() ? Unknown : found->second);
}

Doğru ile operator <<şimdi numarayı şu şekilde kullanabiliriz:

std::cout << MyEnum::AAA << '\n';

Bu da bakımı rahatsız edicidir ve geliştirilebilir, ancak umarım bu fikri elde edersiniz.

Live example


Bu oldukça düzgün görünüyor (uzmanlaşmamış değişkeni tanımlamak mümkün mü?). Belki de bir şeyleri kaçırıyorum, ancak çalışma vakasını nasıl ele aldığını göremiyorum.
user673679

@Paula_plus_plus: Sadece std::arrayhantal bir harita yerine kullanmamalısınız ? Sadece 2 ^ 10 değerlerinden başlayan numaralar için tercih edilecektir. Belki daha da fazlası.
einpoklum

@ einpoklum, çalışma zamanında kaç elemente enumsahip olduğumuzu garanti edebilirsek harika olurdu . Ne yazık ki yapamayız. Haritanın tüm amacı isimleri değerlerle ilişkilendirmektir, ki std::mapbu iyi bir şeydir .
PaperBirdMaster

@Paula_plus_plus: Zaten initialize()argüman sayısı numara değerleri sayısı olan bir fonksiyon çağırıyorsunuz , bu nedenle derleme zamanında değer sayısını biliyorsunuz. Yalnızca yazdırmanız istendiğinde, yalnızca çalışma zamanında bilinen belirli bir değerdir. Ayrıca, bu sayıyı bilmeseniz bile, neredeyse tüm gerçekçi durumlarda, bir std :: vektörü bir std :: haritasından daha hızlı olacaktır.
einpoklum

@einpoklum gerçekten çok iyi bir nokta, bunu düşüneceğim, teşekkürler! Beni endişelendiren tek şey, std::arraybir anahtar / değer konteyneri olmaması ve bu nedenle bulma yöntemlerinden yoksun olması; neyse ben bir düşünce vereceğim.
PaperBirdMaster

7

Senin Eğer enumbakışlar ister

enum MyEnum
{
  AAA = -8,
  BBB = '8',
  CCC = AAA + BBB
};

Öğesinin içeriğini enumyeni bir dosyaya taşıyabilirsiniz :

AAA = -8,
BBB = '8',
CCC = AAA + BBB

Ve sonra değerler bir makro ile çevrelenebilir:

// default definition
#ifned ITEM(X,Y)
#define ITEM(X,Y)
#endif

// Items list
ITEM(AAA,-8)
ITEM(BBB,'8')
ITEM(CCC,AAA+BBB)

// clean up
#undef ITEM

Bir sonraki adım, öğeleri enumtekrar dahil etmek olabilir :

enum MyEnum
{
  #define ITEM(X,Y) X=Y,
  #include "enum_definition_file"
};

Ve son olarak bununla ilgili yardımcı fonksiyonlar üretebilirsiniz enum:

std::string ToString(MyEnum value)
{
  switch( value )
  {
    #define ITEM(X,Y) case X: return #X;
    #include "enum_definition_file"
  }

  return "";
}

MyEnum FromString(std::string const& value)
{
  static std::map<std::string,MyEnum> converter
  {
    #define ITEM(X,Y) { #X, X },
    #include "enum_definition_file"
  };

  auto it = converter.find(value);
  if( it != converter.end() )
    return it->second;
  else
    throw std::runtime_error("Value is missing");
}

Çözüm, eski C ++ standartlarına uygulanabilir ve modern C ++ öğelerini kullanmaz, ancak çok fazla çaba ve bakım gerektirmeden çok sayıda kod üretmek için kullanılabilir.


3
Ayrı bir dosyaya gerek yoktur. Bu aslında bir x-makrosudur .
HolyBlackCat

@HolyBlackCat bazı dosyalardaki çözümü böldüğünüzde numaralandırma değerlerini farklı amaçlar için tekrar kullanabilirsiniz
eferion

Size bir başlıktaki enum tanımının yanında tek bir makroya değerler listesini koyarsanız aynı şeyi yapabileceğinizi söylemeye çalışıyorum.
HolyBlackCat

@HolyBlackCat evet seni anlıyorum ama bu çözümü tercih ediyorum. Öte yandan bu çözüm clang kaynak kodunda bulunabilir, bu yüzden sorunu çözmek için iyi bir yol olduğunu düşünüyorum
eferion

Yeterince adil. Sanırım bunu küçümsememeliydim, çünkü gerçekten bazı kullanımları olabilir. (Pardon the dummy edit, sistem aksi halde oyumu kilitler.)
HolyBlackCat

6

Birkaç gün önce de aynı problemi yaşadım. Bazı tuhaf makro büyüsü olmadan herhangi bir C ++ çözümü bulamadım, bu yüzden bir CMake kod üreteci yazmaya karar verdim basit anahtar durum ifadeleri oluşturmak için .

Kullanımı:

enum2str_generate(
  PATH          <path to place the files in>
  CLASS_NAME    <name of the class (also prefix for the files)>
  FUNC_NAME     <name of the (static) member function>
  NAMESPACE     <the class will be inside this namespace>
  INCLUDES      <LIST of files where the enums are defined>
  ENUMS         <LIST of enums to process>
  BLACKLIST     <LIST of constants to ignore>
  USE_CONSTEXPR <whether to use constexpr or not (default: off)>
  USE_C_STRINGS <whether to use c strings instead of std::string or not (default: off)>
)

İşlev, dosya sistemindeki include dosyalarını arar (include_directories komutuyla sağlanan include dizinlerini kullanır), bunları okur ve sınıfı ve işlevleri oluşturmak için bazı regex işlemleri yapar.

NOT: constexpr, C ++ 'da satır içi anlamına gelir, bu nedenle USE_CONSTEXPR seçeneğinin kullanılması, yalnızca başlık sınıfını oluşturur!

Misal:

./includes/ah:

enum AAA : char { A1, A2 };

typedef enum {
   VAL1          = 0,
   VAL2          = 1,
   VAL3          = 2,
   VAL_FIRST     = VAL1,    // Ignored
   VAL_LAST      = VAL3,    // Ignored
   VAL_DUPLICATE = 1,       // Ignored
   VAL_STRANGE   = VAL2 + 1 // Must be blacklisted
} BBB;

./CMakeLists.txt:

include_directories( ${PROJECT_SOURCE_DIR}/includes ...)

enum2str_generate(
   PATH       "${PROJECT_SOURCE_DIR}"
   CLASS_NAME "enum2Str"
   NAMESPACE  "abc"
   FUNC_NAME  "toStr"
   INCLUDES   "a.h" # WITHOUT directory
   ENUMS      "AAA" "BBB"
   BLACKLIST  "VAL_STRANGE")

üretir:

./enum2Str.hpp:

/*!
  * \file enum2Str.hpp
  * \warning This is an automatically generated file!
  */

#ifndef ENUM2STR_HPP
#define ENUM2STR_HPP

#include <string>
#include <a.h>

namespace abc {

class enum2Str {
 public:
   static std::string toStr( AAA _var ) noexcept;
   static std::string toStr( BBB _var ) noexcept;
};

}

#endif // ENUM2STR_HPP

./enum2Str.cpp:

/*!
  * \file enum2Str.cpp
  * \warning This is an automatically generated file!
  */

#include "enum2Str.hpp"

namespace abc {

/*!
 * \brief Converts the enum AAA to a std::string
 * \param _var The enum value to convert
 * \returns _var converted to a std::string
 */
std::string enum2Str::toStr( AAA _var ) noexcept {
   switch ( _var ) {
      case A1: return "A1";
      case A2: return "A2";
      default: return "<UNKNOWN>";
   }
}

/*!
 * \brief Converts the enum BBB to a std::string
 * \param _var The enum value to convert
 * \returns _var converted to a std::string
 */
std::string enum2Str::toStr( BBB _var ) noexcept {
   switch ( _var ) {
      case VAL1: return "VAL1";
      case VAL2: return "VAL2";
      case VAL3: return "VAL3";
      default: return "<UNKNOWN>";
   }
}
}

Güncelleme:

Komut dosyası artık kapsamlı numaralandırmaları da (enum class | struct) destekliyor ve sık kullandığım diğer komut dosyalarıyla ayrı bir repoya taşıdım: https://github.com/mensinda/cmakeBuildTools


Vaov! Çok özgün ve yenilikçi bir fikir :-) Umarım bir constexprve noexceptsürüm sağlamak için jeneratörünüzü yükseltme cesaretine sahip olursunuz ;-) Ben de sadece GitHub projenize
baktım

1
Jeneratör güncellendi. Şimdi işlevler daima constexpr ve enum olacaktır: <type> artık desteklenmektedir. Yıldız için teşekkürler :)
Mense

Bağlantı koptu ... -.-
yeoman

Bağlantı düzeltildi.
Mense

4

Sadece numaralarınızı oluşturun. Bu amaçla bir jeneratör yazmak yaklaşık beş dakikalık bir çalışmadır.

Cava ve python jeneratör kodu, C ++ dahil olmak üzere istediğiniz herhangi bir dile bağlantı kolaylığı.

Ayrıca istediğiniz herhangi bir işlevsellik ile genişletmek çok kolay.

örnek girdi:

First = 5
Second
Third = 7
Fourth
Fifth=11

oluşturulan başlık:

#include <iosfwd>

enum class Hallo
{
    First = 5,
    Second = 6,
    Third = 7,
    Fourth = 8,
    Fifth = 11
};

std::ostream & operator << (std::ostream &, const Hallo&);

oluşturulan cpp dosyası

#include <ostream>

#include "Hallo.h"

std::ostream & operator << (std::ostream &out, const Hallo&value)
{
    switch(value)
    {
    case Hallo::First:
        out << "First";
        break;
    case Hallo::Second:
        out << "Second";
        break;
    case Hallo::Third:
        out << "Third";
        break;
    case Hallo::Fourth:
        out << "Fourth";
        break;
    case Hallo::Fifth:
        out << "Fifth";
        break;
    default:
        out << "<unknown>";
    }

    return out;
}

Ve jeneratör, taşıma ve uzatma için bir şablon olarak çok kısa bir formda. Bu örnek kod, herhangi bir dosyanın üzerine yazmaktan kaçınmaya çalışır, ancak yine de kendi sorumluluğunuzdadır.

package cppgen;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class EnumGenerator
{
    static void fail(String message)
    {
        System.err.println(message);
        System.exit(1);
    }

    static void run(String[] args)
    throws Exception
    {
        Pattern pattern = Pattern.compile("\\s*(\\w+)\\s*(?:=\\s*(\\d+))?\\s*", Pattern.UNICODE_CHARACTER_CLASS);
        Charset charset = Charset.forName("UTF8");
        String tab = "    ";

        if (args.length != 3)
        {
            fail("Required arguments: <enum name> <input file> <output dir>");
        }

        String enumName = args[0];

        File inputFile = new File(args[1]);

        if (inputFile.isFile() == false)
        {
            fail("Not a file: [" + inputFile.getCanonicalPath() + "]");
        }

        File outputDir = new File(args[2]);

        if (outputDir.isDirectory() == false)
        {
            fail("Not a directory: [" + outputDir.getCanonicalPath() + "]");
        }

        File headerFile = new File(outputDir, enumName + ".h");
        File codeFile = new File(outputDir, enumName + ".cpp");

        for (File file : new File[] { headerFile, codeFile })
        {
            if (file.exists())
            {
                fail("Will not overwrite file [" + file.getCanonicalPath() + "]");
            }
        }

        int nextValue = 0;

        Map<String, Integer> fields = new LinkedHashMap<>();

        try
        (
            BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(inputFile), charset));
        )
        {
            while (true)
            {
                String line = reader.readLine();

                if (line == null)
                {
                    break;
                }

                if (line.trim().length() == 0)
                {
                    continue;
                }

                Matcher matcher = pattern.matcher(line);

                if (matcher.matches() == false)
                {
                    fail("Syntax error: [" + line + "]");
                }

                String fieldName = matcher.group(1);

                if (fields.containsKey(fieldName))
                {
                    fail("Double fiend name: " + fieldName);
                }

                String valueString = matcher.group(2);

                if (valueString != null)
                {
                    int value = Integer.parseInt(valueString);

                    if (value < nextValue)
                    {
                        fail("Not a monotonous progression from " + nextValue + " to " + value + " for enum field " + fieldName);
                    }

                    nextValue = value;
                }

                fields.put(fieldName, nextValue);

                ++nextValue;
            }
        }

        try
        (
            PrintWriter headerWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(headerFile), charset));
            PrintWriter codeWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(codeFile), charset));
        )
        {
            headerWriter.println();
            headerWriter.println("#include <iosfwd>");
            headerWriter.println();
            headerWriter.println("enum class " + enumName);
            headerWriter.println('{');
            boolean first = true;
            for (Entry<String, Integer> entry : fields.entrySet())
            {
                if (first == false)
                {
                    headerWriter.println(",");
                }

                headerWriter.print(tab + entry.getKey() + " = " + entry.getValue());

                first = false;
            }
            if (first == false)
            {
                headerWriter.println();
            }
            headerWriter.println("};");
            headerWriter.println();
            headerWriter.println("std::ostream & operator << (std::ostream &, const " + enumName + "&);");
            headerWriter.println();

            codeWriter.println();
            codeWriter.println("#include <ostream>");
            codeWriter.println();
            codeWriter.println("#include \"" + enumName + ".h\"");
            codeWriter.println();
            codeWriter.println("std::ostream & operator << (std::ostream &out, const " + enumName + "&value)");
            codeWriter.println('{');
            codeWriter.println(tab + "switch(value)");
            codeWriter.println(tab + '{');
            first = true;
            for (Entry<String, Integer> entry : fields.entrySet())
            {
                codeWriter.println(tab + "case " + enumName + "::" + entry.getKey() + ':');
                codeWriter.println(tab + tab + "out << \"" + entry.getKey() + "\";");
                codeWriter.println(tab + tab + "break;");

                first = false;
            }
            codeWriter.println(tab + "default:");
            codeWriter.println(tab + tab + "out << \"<unknown>\";");
            codeWriter.println(tab + '}');
            codeWriter.println();
            codeWriter.println(tab + "return out;");
            codeWriter.println('}');
            codeWriter.println();
        }
    }

    public static void main(String[] args)
    {
        try
        {
            run(args);
        }
        catch(Exception exc)
        {
            exc.printStackTrace();
            System.exit(1);
        }
    }
}

Ve Python 3.5'e bir liman çünkü potansiyel olarak yardımcı olacak kadar farklı

import re
import collections
import sys
import io
import os

def fail(*args):
    print(*args)
    exit(1)

pattern = re.compile(r'\s*(\w+)\s*(?:=\s*(\d+))?\s*')
tab = "    "

if len(sys.argv) != 4:
    n=0
    for arg in sys.argv:
        print("arg", n, ":", arg, " / ", sys.argv[n])
        n += 1
    fail("Required arguments: <enum name> <input file> <output dir>")

enumName = sys.argv[1]

inputFile = sys.argv[2]

if not os.path.isfile(inputFile):
    fail("Not a file: [" + os.path.abspath(inputFile) + "]")

outputDir = sys.argv[3]

if not os.path.isdir(outputDir):
    fail("Not a directory: [" + os.path.abspath(outputDir) + "]")

headerFile = os.path.join(outputDir, enumName + ".h")
codeFile = os.path.join(outputDir, enumName + ".cpp")

for file in [ headerFile, codeFile ]:
    if os.path.exists(file):
        fail("Will not overwrite file [" + os.path.abspath(file) + "]")

nextValue = 0

fields = collections.OrderedDict()

for line in open(inputFile, 'r'):
    line = line.strip()

    if len(line) == 0:
        continue

    match = pattern.match(line)

    if match == None:
        fail("Syntax error: [" + line + "]")

    fieldName = match.group(1)

    if fieldName in fields:
        fail("Double field name: " + fieldName)

    valueString = match.group(2)

    if valueString != None:
        value = int(valueString)

        if value < nextValue:
            fail("Not a monotonous progression from " + nextValue + " to " + value + " for enum field " + fieldName)

        nextValue = value

    fields[fieldName] = nextValue

    nextValue += 1

headerWriter = open(headerFile, 'w')
codeWriter = open(codeFile, 'w')

try:
    headerWriter.write("\n")
    headerWriter.write("#include <iosfwd>\n")
    headerWriter.write("\n")
    headerWriter.write("enum class " + enumName + "\n")
    headerWriter.write("{\n")
    first = True
    for fieldName, fieldValue in fields.items():
        if not first:
            headerWriter.write(",\n")

        headerWriter.write(tab + fieldName + " = " + str(fieldValue))

        first = False
    if not first:
        headerWriter.write("\n")
    headerWriter.write("};\n")
    headerWriter.write("\n")
    headerWriter.write("std::ostream & operator << (std::ostream &, const " + enumName + "&);\n")
    headerWriter.write("\n")

    codeWriter.write("\n")
    codeWriter.write("#include <ostream>\n")
    codeWriter.write("\n")
    codeWriter.write("#include \"" + enumName + ".h\"\n")
    codeWriter.write("\n")
    codeWriter.write("std::ostream & operator << (std::ostream &out, const " + enumName + "&value)\n")
    codeWriter.write("{\n")
    codeWriter.write(tab + "switch(value)\n")
    codeWriter.write(tab + "{\n")
    for fieldName in fields.keys():
        codeWriter.write(tab + "case " + enumName + "::" + fieldName + ":\n")
        codeWriter.write(tab + tab + "out << \"" + fieldName + "\";\n")
        codeWriter.write(tab + tab + "break;\n")
    codeWriter.write(tab + "default:\n")
    codeWriter.write(tab + tab + "out << \"<unknown>\";\n")
    codeWriter.write(tab + "}\n")
    codeWriter.write("\n")
    codeWriter.write(tab + "return out;\n")
    codeWriter.write("}\n")
    codeWriter.write("\n")
finally:
    headerWriter.close()
    codeWriter.close()

1
Jeneratörünüzü iki dilde paylaştığınız için çok teşekkür ederim :-) Ama derleme zamanında nasıl üreteceğiniz hakkında bir fikriniz var mı? Örneğin, giriş verileri değiştirildiğinde C ++ tarafından üretilen kodu yenilemek için jeneratörünüzü CMake deyimleri kullanarak çevirmeyi hayal edebilir miyiz? Benim hayalim C ++ derleyicisini meta-programlama ( variadic template classve constexprişlevleri) kullanarak derlemede numaralandırmalar oluşturmaya zorlamak .
olibre

Otoh, özel bir cmake komutu eklemenin çok zahmetli olması durumunda, IDE'nizi otomatikleştirebilir veya jeneratörü manuel olarak çağırabilir ve çıktıyı kaynak kontrolüne alabilirsiniz. Bazen çok fazla olmadığı sürece kaynak kontrolünde kod oluşturmak iyi bir fikirdir ve insanlar manuel değişiklikler yapmaları gerekmediğini anlarlar, çünkü bazen, oluşturulan dosyaların geçmişine bakmak ilginçtir. 'garip bir şey hata ayıklama ve jeneratör yakın zamanda bir değişiklik bir şey kırmış olabilir şüphesi var :)
yeoman

Derleme zamanında bir şeyler üretmek hakkında, LISP'de bu çok kolay çünkü sözdizimi çok temiz ve kolaydır. Bu, dinamik olarak yazılması gerçeğine yardımcı olur, bu da çok fazla sözdizimi olmadan kısa ve okunabilir olmasını sağlar. C ++ 'daki LISP makrolarının eşdeğeri, oluşturmaya çalıştığınız şeyin AST'sini tanımlamak için çok karmaşık bir yola ihtiyaç duyacaktır. Ve C ++ için bir AST asla güzel değildir :(
yeoman

Doğrudan cmake yerine Make, süper kolay btw. Her bir .enum dosyası için find yoluyla .h ve .cpp hedefleri oluşturun ve bu hedeflerin adı geçen numaralandırma def'lerine bağlı olmasını sağlayın, böylece .enum def dosyaları değiştiğinde otomatik olarak yeniden oluşturulur. Cmake'de muhtemelen çok daha kolay çünkü bu tür şeyler için sihirle dolu ama düzenli olarak Make, ant ve gradle kullanıyorum, ancak Maven, cmake ve grunt hakkında sınırlı bilgiye sahibim :)
yeoman

Cevabınız için teşekkürler :-) Bence C ++ geliştiricileri çoğu jeneratör doğrudan C ++ kodu gibi enum class Hallo{ First=5, Second=6, Third=7, Fourth=8};veya birkaç satır içinde enums tespit edebilir eğer takdir edecektir :-D enumBir C ++ içinde tespit etmek için jeneratör uyarlayabilirsiniz düşünüyorum dosya? En iyisi sadece bir etiketin algılanması üzerine kod oluşturmak olabilir /*<Generate enum to string here>*/. Daha sonra üreteciniz, karşılık gelen C ++ tarafından üretilen kodu (daha önce oluşturulan kodu değiştirerek) yerinde yazar. ^ _ ^ Ne harika bir jeneratör değil mi? Şerefe :-)
olibre

3

OP'nin isteğine göre, burada Boost Preprosessor ve Variadic Makrolarına dayanan çirkin makro çözümünün soyulmuş bir versiyonu .

Numaralandırıcı öğelerin sözdizimi gibi basit bir listenin yanı sıra belirli öğeler için ayar değerleri sağlar.

XXX_ENUM(foo,(a,b,(c,42)));

genişler

enum foo {
    a,
    b,
    c=42
};

Yanı sıra gerekli çıktıları ve geri dönüşüm yapmak için gerekli fonksiyonları ile. Bu makro uzun zamandır burada ve en etkili yol olduğundan ya da uygun bir yol olduğundan emin değilim, ama o zamandan beri çalışıyor

Tam kod hem de eylem görülebilir Ideone ve Coliru .

Onun devasa çirkinliği yukarıda; Gözlerini korumak için spoylerlerin arkasına koyardım, eğer biliyorsam, ama markdown benden hoşlanmıyor.

Kütüphane (tek bir başlık dosyasında birleştirildi)

#include <boost/preprocessor.hpp>
#include <string>
#include <unordered_map>

namespace xxx
{

template<class T>
struct enum_cast_adl_helper { };

template<class E>
E enum_cast( const std::string& s )
{
    return do_enum_cast(s,enum_cast_adl_helper<E>());
}

template<class E>
E enum_cast( const char* cs )
{
    std::string s(cs);
    return enum_cast<E>(s);
}

} // namespace xxx

#define XXX_PP_ARG_N(                             \
          _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
         _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
         _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
         _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
         _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
         _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
         _61,_62,_63,N,...) N

#define XXX_PP_RSEQ_N()                 \
         63,62,61,60,                   \
         59,58,57,56,55,54,53,52,51,50, \
         49,48,47,46,45,44,43,42,41,40, \
         39,38,37,36,35,34,33,32,31,30, \
         29,28,27,26,25,24,23,22,21,20, \
         19,18,17,16,15,14,13,12,11,10, \
         9,8,7,6,5,4,3,2,1,0 

#define XXX_PP_NARG_(...) XXX_PP_ARG_N(__VA_ARGS__)
#define XXX_PP_NARG(...)  XXX_PP_NARG_(__VA_ARGS__,XXX_PP_RSEQ_N())
#define XXX_TUPLE_SIZE_INTERNAL(TUPLE) XXX_PP_NARG TUPLE

#define XXX_TUPLE_CHOICE(i)                            \
  BOOST_PP_APPLY(                                      \
    BOOST_PP_TUPLE_ELEM(                               \
      25, i, (                                         \
        (0), (1), (2), (3), (4), (5), (6), (7), (8),   \
        (9), (10), (11), (12), (13), (14), (15), (16), \
        (17), (18), (19), (20), (21), (22), (23), (24) \
  ) ) )

#define BOOST_PP_BOOL_00  BOOST_PP_BOOL_0
#define BOOST_PP_BOOL_01  BOOST_PP_BOOL_1
#define BOOST_PP_BOOL_02  BOOST_PP_BOOL_2
#define BOOST_PP_BOOL_03  BOOST_PP_BOOL_3
#define BOOST_PP_BOOL_04  BOOST_PP_BOOL_4
#define BOOST_PP_BOOL_05  BOOST_PP_BOOL_5
#define BOOST_PP_BOOL_06  BOOST_PP_BOOL_6
#define BOOST_PP_BOOL_07  BOOST_PP_BOOL_7
#define BOOST_PP_BOOL_08  BOOST_PP_BOOL_8
#define BOOST_PP_BOOL_09  BOOST_PP_BOOL_9
#define BOOST_PP_BOOL_010 BOOST_PP_BOOL_10
#define BOOST_PP_BOOL_011 BOOST_PP_BOOL_11
#define BOOST_PP_BOOL_012 BOOST_PP_BOOL_12
#define BOOST_PP_BOOL_013 BOOST_PP_BOOL_13
#define BOOST_PP_BOOL_014 BOOST_PP_BOOL_14
#define BOOST_PP_BOOL_015 BOOST_PP_BOOL_15
#define BOOST_PP_BOOL_016 BOOST_PP_BOOL_16
#define BOOST_PP_BOOL_017 BOOST_PP_BOOL_17
#define BOOST_PP_BOOL_018 BOOST_PP_BOOL_18
#define BOOST_PP_BOOL_019 BOOST_PP_BOOL_19
#define BOOST_PP_BOOL_020 BOOST_PP_BOOL_20
#define BOOST_PP_BOOL_021 BOOST_PP_BOOL_21
#define BOOST_PP_BOOL_022 BOOST_PP_BOOL_22
#define BOOST_PP_BOOL_023 BOOST_PP_BOOL_23
#define BOOST_PP_BOOL_024 BOOST_PP_BOOL_24
#define BOOST_PP_BOOL_025 BOOST_PP_BOOL_25
#define BOOST_PP_BOOL_026 BOOST_PP_BOOL_26
#define BOOST_PP_BOOL_027 BOOST_PP_BOOL_27
#define BOOST_PP_BOOL_028 BOOST_PP_BOOL_28
#define BOOST_PP_BOOL_029 BOOST_PP_BOOL_29
#define BOOST_PP_BOOL_030 BOOST_PP_BOOL_30
#define BOOST_PP_BOOL_031 BOOST_PP_BOOL_31
#define BOOST_PP_BOOL_032 BOOST_PP_BOOL_32
#define BOOST_PP_BOOL_033 BOOST_PP_BOOL_33
#define BOOST_PP_BOOL_034 BOOST_PP_BOOL_34
#define BOOST_PP_BOOL_035 BOOST_PP_BOOL_35
#define BOOST_PP_BOOL_036 BOOST_PP_BOOL_36
#define BOOST_PP_BOOL_037 BOOST_PP_BOOL_37
#define BOOST_PP_BOOL_038 BOOST_PP_BOOL_38
#define BOOST_PP_BOOL_039 BOOST_PP_BOOL_39
#define BOOST_PP_BOOL_040 BOOST_PP_BOOL_40
#define BOOST_PP_BOOL_041 BOOST_PP_BOOL_41
#define BOOST_PP_BOOL_042 BOOST_PP_BOOL_42
#define BOOST_PP_BOOL_043 BOOST_PP_BOOL_43
#define BOOST_PP_BOOL_044 BOOST_PP_BOOL_44
#define BOOST_PP_BOOL_045 BOOST_PP_BOOL_45
#define BOOST_PP_BOOL_046 BOOST_PP_BOOL_46
#define BOOST_PP_BOOL_047 BOOST_PP_BOOL_47
#define BOOST_PP_BOOL_048 BOOST_PP_BOOL_48
#define BOOST_PP_BOOL_049 BOOST_PP_BOOL_49
#define BOOST_PP_BOOL_050 BOOST_PP_BOOL_50
#define BOOST_PP_BOOL_051 BOOST_PP_BOOL_51
#define BOOST_PP_BOOL_052 BOOST_PP_BOOL_52
#define BOOST_PP_BOOL_053 BOOST_PP_BOOL_53
#define BOOST_PP_BOOL_054 BOOST_PP_BOOL_54
#define BOOST_PP_BOOL_055 BOOST_PP_BOOL_55
#define BOOST_PP_BOOL_056 BOOST_PP_BOOL_56
#define BOOST_PP_BOOL_057 BOOST_PP_BOOL_57
#define BOOST_PP_BOOL_058 BOOST_PP_BOOL_58
#define BOOST_PP_BOOL_059 BOOST_PP_BOOL_59
#define BOOST_PP_BOOL_060 BOOST_PP_BOOL_60
#define BOOST_PP_BOOL_061 BOOST_PP_BOOL_61
#define BOOST_PP_BOOL_062 BOOST_PP_BOOL_62
#define BOOST_PP_BOOL_063 BOOST_PP_BOOL_63

#define BOOST_PP_DEC_00  BOOST_PP_DEC_0
#define BOOST_PP_DEC_01  BOOST_PP_DEC_1
#define BOOST_PP_DEC_02  BOOST_PP_DEC_2
#define BOOST_PP_DEC_03  BOOST_PP_DEC_3
#define BOOST_PP_DEC_04  BOOST_PP_DEC_4
#define BOOST_PP_DEC_05  BOOST_PP_DEC_5
#define BOOST_PP_DEC_06  BOOST_PP_DEC_6
#define BOOST_PP_DEC_07  BOOST_PP_DEC_7
#define BOOST_PP_DEC_08  BOOST_PP_DEC_8
#define BOOST_PP_DEC_09  BOOST_PP_DEC_9
#define BOOST_PP_DEC_010 BOOST_PP_DEC_10
#define BOOST_PP_DEC_011 BOOST_PP_DEC_11
#define BOOST_PP_DEC_012 BOOST_PP_DEC_12
#define BOOST_PP_DEC_013 BOOST_PP_DEC_13
#define BOOST_PP_DEC_014 BOOST_PP_DEC_14
#define BOOST_PP_DEC_015 BOOST_PP_DEC_15
#define BOOST_PP_DEC_016 BOOST_PP_DEC_16
#define BOOST_PP_DEC_017 BOOST_PP_DEC_17
#define BOOST_PP_DEC_018 BOOST_PP_DEC_18
#define BOOST_PP_DEC_019 BOOST_PP_DEC_19
#define BOOST_PP_DEC_020 BOOST_PP_DEC_20
#define BOOST_PP_DEC_021 BOOST_PP_DEC_21
#define BOOST_PP_DEC_022 BOOST_PP_DEC_22
#define BOOST_PP_DEC_023 BOOST_PP_DEC_23
#define BOOST_PP_DEC_024 BOOST_PP_DEC_24
#define BOOST_PP_DEC_025 BOOST_PP_DEC_25
#define BOOST_PP_DEC_026 BOOST_PP_DEC_26
#define BOOST_PP_DEC_027 BOOST_PP_DEC_27
#define BOOST_PP_DEC_028 BOOST_PP_DEC_28
#define BOOST_PP_DEC_029 BOOST_PP_DEC_29
#define BOOST_PP_DEC_030 BOOST_PP_DEC_30
#define BOOST_PP_DEC_031 BOOST_PP_DEC_31
#define BOOST_PP_DEC_032 BOOST_PP_DEC_32
#define BOOST_PP_DEC_033 BOOST_PP_DEC_33
#define BOOST_PP_DEC_034 BOOST_PP_DEC_34
#define BOOST_PP_DEC_035 BOOST_PP_DEC_35
#define BOOST_PP_DEC_036 BOOST_PP_DEC_36
#define BOOST_PP_DEC_037 BOOST_PP_DEC_37
#define BOOST_PP_DEC_038 BOOST_PP_DEC_38
#define BOOST_PP_DEC_039 BOOST_PP_DEC_39
#define BOOST_PP_DEC_040 BOOST_PP_DEC_40
#define BOOST_PP_DEC_041 BOOST_PP_DEC_41
#define BOOST_PP_DEC_042 BOOST_PP_DEC_42
#define BOOST_PP_DEC_043 BOOST_PP_DEC_43
#define BOOST_PP_DEC_044 BOOST_PP_DEC_44
#define BOOST_PP_DEC_045 BOOST_PP_DEC_45
#define BOOST_PP_DEC_046 BOOST_PP_DEC_46
#define BOOST_PP_DEC_047 BOOST_PP_DEC_47
#define BOOST_PP_DEC_048 BOOST_PP_DEC_48
#define BOOST_PP_DEC_049 BOOST_PP_DEC_49
#define BOOST_PP_DEC_050 BOOST_PP_DEC_50
#define BOOST_PP_DEC_051 BOOST_PP_DEC_51
#define BOOST_PP_DEC_052 BOOST_PP_DEC_52
#define BOOST_PP_DEC_053 BOOST_PP_DEC_53
#define BOOST_PP_DEC_054 BOOST_PP_DEC_54
#define BOOST_PP_DEC_055 BOOST_PP_DEC_55
#define BOOST_PP_DEC_056 BOOST_PP_DEC_56
#define BOOST_PP_DEC_057 BOOST_PP_DEC_57
#define BOOST_PP_DEC_058 BOOST_PP_DEC_58
#define BOOST_PP_DEC_059 BOOST_PP_DEC_59
#define BOOST_PP_DEC_060 BOOST_PP_DEC_60
#define BOOST_PP_DEC_061 BOOST_PP_DEC_61
#define BOOST_PP_DEC_062 BOOST_PP_DEC_62
#define BOOST_PP_DEC_063 BOOST_PP_DEC_63

#define XXX_TO_NUMx(x) 0 ## x
#define XXX_TO_NUM(x) BOOST_PP_ADD(0,XXX_TO_NUMx(x))
#define XXX_STRINGIZEX(x) # x
#define XXX_VSTRINGIZE_SINGLE(a,b,x) XXX_STRINGIZE(x)
#define XXX_VSTRINGIZE_TUPLE(tpl) XXX_TUPLE_FOR_EACH(XXX_VSTRINGIZE_SINGLE,,tpl)
#define XXX_TUPLE_SIZE(TUPLE) XXX_TO_NUM(XXX_TUPLE_CHOICE(XXX_TUPLE_SIZE_INTERNAL(TUPLE)))
#define XXX_TUPLE_FOR_EACH(MACRO,DATA,TUPLE) BOOST_PP_LIST_FOR_EACH(MACRO,DATA,BOOST_PP_TUPLE_TO_LIST(XXX_TUPLE_SIZE(TUPLE),TUPLE))
#define XXX_STRINGIZE(x) XXX_STRINGIZEX(x)
#define XXX_VSTRINGIZE(...) XXX_VSTRINGIZE_TUPLE((__VA_ARGS__))
#define XXX_CAST_TO_VOID_ELEMENT(r,data,elem) (void)(elem);
#define XXX_CAST_TO_VOID_INTERNAL(TUPLE) XXX_TUPLE_FOR_EACH(XXX_CAST_TO_VOID_ELEMENT,,TUPLE)    
#define XXX_CAST_TO_VOID(...) XXX_CAST_TO_VOID_INTERNAL((__VA_ARGS__))
#define XXX_ENUM_EXTRACT_SP(en) BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),0,en) = BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),1,en)
#define XXX_ENUM_ELEMENT(r,data,elem) BOOST_PP_IF( XXX_TUPLE_SIZE(elem), XXX_ENUM_EXTRACT_SP(elem), elem) ,
#define XXX_ENUM_EXTRACT_ELEMENT(en) BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),0,en)
#define XXX_ENUM_CASE_ELEMENT(en) BOOST_PP_IF( XXX_TUPLE_SIZE(en), XXX_ENUM_EXTRACT_ELEMENT(en), en )
#define XXX_ENUM_CASE(r,data,elem) case data :: XXX_ENUM_CASE_ELEMENT(elem) : return #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem));
#define XXX_ENUM_IFELSE(r,data,elem) else if( en == data :: XXX_ENUM_CASE_ELEMENT(elem)) { return #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)); }
#define XXX_ENUM_CASTLIST(r,data,elem) { XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)), data :: XXX_ENUM_CASE_ELEMENT(elem) },
#define XXX_ENUM_QUALIFIED_CASTLIST(r,data,elem) { #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)), data :: XXX_ENUM_CASE_ELEMENT(elem) },

#define XXX_ENUM_INTERNAL(TYPE,NAME,TUPLE)                       \
enum TYPE                                                        \
{                                                                \
   XXX_TUPLE_FOR_EACH(XXX_ENUM_ELEMENT,,TUPLE)                   \
   BOOST_PP_CAT(last_enum_,NAME)                                 \
};                                                               \
                                                                 \
inline                                                           \
const char* to_string( NAME en )                                 \
{                                                                \
   if(false)                                                     \
   {                                                             \
   }                                                             \
   XXX_TUPLE_FOR_EACH(XXX_ENUM_IFELSE,NAME,TUPLE)                \
   else if( en == NAME :: BOOST_PP_CAT(last_enum_,NAME) )        \
   {                                                             \
     return XXX_VSTRINGIZE(NAME,::,BOOST_PP_CAT(last_enum_,NAME));  \
   }                                                             \
   else                                                          \
   {                                                             \
     return "Invalid enum value specified for " # NAME;          \
   }                                                             \
}                                                                \
                                                                 \
inline                                                           \
std::ostream& operator<<( std::ostream& os, const NAME& en )     \
{                                                                \
   os << to_string(en);                                          \
   return os;                                                    \
}                                                                \
                                                                 \
inline                                                           \
NAME do_enum_cast( const std::string& s, const ::xxx::enum_cast_adl_helper<NAME>& ) \
{                                                                \
  static const std::unordered_map<std::string,NAME> map =        \
  {                                                              \
    XXX_TUPLE_FOR_EACH(XXX_ENUM_CASTLIST,NAME,TUPLE)             \
    XXX_TUPLE_FOR_EACH(XXX_ENUM_QUALIFIED_CASTLIST,NAME,TUPLE)   \
  };                                                             \
                                                                 \
  auto cit = map.find(s);                                        \
  if( cit == map.end() )                                         \
  {                                                              \
    throw std::runtime_error("Invalid value to cast to enum");   \
  }                                                              \
  return cit->second;                                            \
}

#define XXX_ENUM(NAME,TUPLE) XXX_ENUM_INTERNAL(NAME,NAME,TUPLE)
#define XXX_ENUM_CLASS(NAME,TUPLE) XXX_ENUM_INTERNAL(class NAME,NAME,TUPLE)
#define XXX_ENUM_CLASS_TYPE(NAME,TYPE,TUPLE) XXX_ENUM_INTERNAL(class NAME : TYPE,NAME,TUPLE)
#define XXX_ENUM_TYPE(NAME,TYPE,TUPLE) XXX_ENUM_INTERNAL(NAME : TYPE,NAME,TUPLE)

kullanım

#include "xxx_enum.h"  // the above lib
#include <iostream>

XXX_ENUM(foo,(a,b,(c,42)));

int main()
{
  std::cout << "foo::a = "            << foo::a            <<'\n';
  std::cout << "(int)foo::c = "       << (int)foo::c       <<'\n';
  std::cout << "to_string(foo::b) = " << to_string(foo::b) <<'\n';
  std::cout << "xxx::enum_cast<foo>(\"b\") = " << xxx::enum_cast<foo>("b") <<'\n';
}

Derleme (içindeki kopyala yapıştır başlığı main.cpp)

> g++ --version | sed 1q
g++ (GCC) 4.9.2

> g++ -std=c++14 -pedantic -Wall -Wextra main.cpp
main.cpp:268:31: warning: extra ';' [-Wpedantic]
     XXX_ENUM(foo,(a,b,(c,42)));
                               ^

Çıktı

foo::a = foo::a
(int)foo::c = 42
to_string(foo::b) = foo::b
xxx::enum_cast<foo>("b") = foo::b

5
Bu kod bloğu, meta programlamanın kara büyüsünün muhteşem manzaraları boyunca çılgın bir yolculuk. Aslında ulaştığımda kendimi rahatlamış hissettim main- Evim, güzel evim!
Quentin

Çıkışı kontrol etmek için coliru'ya bir bağlantı ekledim (bazı uyarılar var, cevabınızdaki bağlantıya tıklayın). Ayrıca Lib / Usage olarak ayrıldım. Bu şeyler namespace xxxbaşlık yerine taşınabilir mi? Girişte kullanımınızı söyleyebilir boost/preprocessor.hppve bu nedenle cevap modern C ++ uyumludur . Daha iyi kalite için lütfen uyarıları düzeltin ve kaynak kodunu biraz temizleyin.
olibre

@olibre: Bence kütüphanemizdeki 5 farklı başlık bence koppastad. Enum_cast daha genel bir parçasıdır ama makroda do_enum_cast ne olduğunu görmek için de eklemek için düşündüm .. Uyarılar sadece benim main<tab>vim kullanmıyorum argümanlar dahil. Bu kod gerçekten temizlenebilir sanmıyorum, sadece ne yapılabilir ve olmamalı göstermek gerekir;) ve burada değiştirirseniz artık üretimde kullandığım kod değil ... bu kırılgan şeylerden biridir Bir kez çalıştığında asla dokunmamanız daha iyi olur çünkü hiç kimsenin tahmin edemeyeceği şekilde çökebilir.
PlazmaHH

Pekala, Plazma, bunun bir Kavram Kanıtı olarak görülebileceğini düşünüyorum . Ancak yukarı oylanacak çok fazla makro yükü var. Yine de paylaştığınız için teşekkürler. Şerefe
olibre

Merhaba Plazma. Derleme ve çalıştırma çıktısı ile tamamlanan derin bir kaynak kodu temizleme + gerçekleştirdim. Lütfen düzenlememi kontrol et . Umarım bu senin için uygundur. Cevap daha değerli mi? Ancak, makro yükü hala korkunç! İyi günler :-) Şerefe
olibre

2

Aşağıdaki çözüm, std::array<std::string,N>verilen bir numaralandırma için a'ya dayanmaktadır .

İçin enumiçin std::stringdönüşüm biz sadece enum yayınlayabileceğim size_tve diziden dizesinde arama. İşlem O (1) dir ve yığın tahsisi gerektirmez.

#include <boost/preprocessor/seq/transform.hpp>
#include <boost/preprocessor/seq/enum.hpp>
#include <boost/preprocessor/stringize.hpp>

#include <string>
#include <array>
#include <iostream>

#define STRINGIZE(s, data, elem) BOOST_PP_STRINGIZE(elem)

// ENUM
// ============================================================================
#define ENUM(X, SEQ) \
struct X {   \
    enum Enum {BOOST_PP_SEQ_ENUM(SEQ)}; \
    static const std::array<std::string,BOOST_PP_SEQ_SIZE(SEQ)> array_of_strings() { \
        return {{BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(STRINGIZE, 0, SEQ))}}; \
    } \
    static std::string to_string(Enum e) { \
        auto a = array_of_strings(); \
        return a[static_cast<size_t>(e)]; \
    } \
}

İçin std::stringiçin enumdönüşüm biz dizisi üzerinde doğrusal bir arama yapmak zorunda kalacak ve dizi indeksi dökümenum .

Kullanım örnekleriyle buradan deneyin: http://coliru.stacked-crooked.com/a/e4212f93bee65076

Düzenleme: Özel Numaralandırma bir sınıf içinde kullanılabilir böylece benim çözüm elden.


İlginç cevabınız için teşekkür ederim. Lütfen makronuzu bir sınıf içinde kullanmak için teklifinizi yeniden düzenleyin. Bkz coliru.stacked-crooked.com/a/00d362eba836d04b Üstelik kullanmayı deneyin constexprve noexeptmümkünse anahtar kelimeler. Şerefe :-)
olibre

Soru bu gerekliliği belirtmedi.
FKaria

Soru güncellendi (örneğe bakın). Diğer iki gereklilik: (1) destek tipi enum ve (2) değerler, sıra 0, 1, 2 ... 'den farklı olabilir
olibre

Bir sınıf içinde kullanılabilecek çözümümü elden geçirdim. Değerlerin 0,1,2'den nasıl farklı hale getirileceğini anlayamadım.
FKaria

Merhaba FKaria. Çalışmanız için çok teşekkür ederim. Aynı sınıfta birkaç numarayı desteklemek ve enum class X : Typeformatı desteklemek için bazı değişiklikler yaptım . Lütfen katkımı gözden geçirin : coliru.stacked-crooked.com/a/b02db9190d3491a3 Değişikliklerim hakkında ne düşünüyorsunuz? Enum içinde ayarlanmış değerleri destekleme konusunda herhangi bir fikriniz var mı? Örnek enum E{A=3, B=6, C=A-B};Şerefe
olibre

2

Bu öz C ++ variadic şablonlara dayalı basit bir eşleme sağlar.

Bu, özgeçmişten tür tabanlı haritanın C ++ 17 basitleştirilmiş bir sürümüdür :

#include <cstring> // http://stackoverflow.com/q/24520781

template<typename KeyValue, typename ... RestOfKeyValues>
struct map {
  static constexpr typename KeyValue::key_t get(const char* val) noexcept {
    if constexpr (sizeof...(RestOfKeyValues)==0)  // C++17 if constexpr
      return KeyValue::key; // Returns last element
    else {
      static_assert(KeyValue::val != nullptr,
                  "Only last element may have null name");
      return strcmp(val, KeyValue::val()) 
            ? map<RestOfKeyValues...>::get(val) : KeyValue::key;
    }
  }
  static constexpr const char* get(typename KeyValue::key_t key) noexcept {
    if constexpr (sizeof...(RestOfKeyValues)==0)
      return (KeyValue::val != nullptr) && (key == KeyValue::key)
            ? KeyValue::val() : "";
    else
      return (key == KeyValue::key) 
            ? KeyValue::val() : map<RestOfKeyValues...>::get(key);
  }
};

template<typename Enum, typename ... KeyValues>
class names {
  typedef map<KeyValues...> Map;
public:
  static constexpr Enum get(const char* nam) noexcept {
    return Map::get(nam);
  }
  static constexpr const char* get(Enum key) noexcept {
    return Map::get(key);
  }
};

Örnek bir kullanım:

enum class fasion {
    fancy,
    classic,
    sporty,
    emo,
    __last__ = emo,
    __unknown__ = -1
};

#define NAME(s) static inline constexpr const char* s() noexcept {return #s;}
namespace name {
    NAME(fancy)
    NAME(classic)
    NAME(sporty)
    NAME(emo)
}

template<auto K, const char* (*V)()>  // C++17 template<auto>
struct _ {
    typedef decltype(K) key_t;
    typedef decltype(V) name_t;
    static constexpr key_t  key = K; // enum id value
    static constexpr name_t val = V; // enum id name
};

typedef names<fasion,
    _<fasion::fancy, name::fancy>,
    _<fasion::classic, name::classic>,
    _<fasion::sporty, name::sporty>,
    _<fasion::emo, name::emo>,
    _<fasion::__unknown__, nullptr>
> fasion_names;

Her map<KeyValues...>iki yönde de kullanılabilir:

  • fasion_names::get(fasion::emo)
  • fasion_names::get("emo")

Bu örnek godbolt.org adresinde mevcuttur.

int main ()
{
  constexpr auto str = fasion_names::get(fasion::emo);
  constexpr auto fsn = fasion_names::get(str);
  return (int) fsn;
}

Sonuç gcc-7 -std=c++1z -Ofast -S

main:
        mov     eax, 3
        ret

1
Çok ilginç bir meta-programlama yolu. Biraz özerk olmak için cevabı basitleştirmeye çalıştım (Gist bağlantısına bağımlı olmadan). Kısa ve anlaşılır olabilmek için nihayet cevabınızı çok düzenledim. Değişikliklerime hala katılıyor musunuz? Şerefe ;-)
olibre

2

Düzgün bir şekilde dizeye dönüştürülen bir tür alma sorunu ile birlikte, uzun bir süre için bu sorun tarafından sinirli olmuştur. Ancak, son sorun için, standart C ++ 'da bir değişkenin tipini yazdırmak mümkün mü? , fikri kullanarak C ++ tür adlarını bir constexpr şekilde alabilir miyim? . Bu teknik kullanılarak, bir enum değerini dize olarak almak için benzer bir fonksiyon oluşturulabilir:

#include <iostream>
using namespace std;

class static_string
{
    const char* const p_;
    const std::size_t sz_;

public:
    typedef const char* const_iterator;

    template <std::size_t N>
    constexpr static_string(const char(&a)[N]) noexcept
        : p_(a)
        , sz_(N - 1)
    {}

    constexpr static_string(const char* p, std::size_t N) noexcept
        : p_(p)
        , sz_(N)
    {}

    constexpr const char* data() const noexcept { return p_; }
    constexpr std::size_t size() const noexcept { return sz_; }

    constexpr const_iterator begin() const noexcept { return p_; }
    constexpr const_iterator end()   const noexcept { return p_ + sz_; }

    constexpr char operator[](std::size_t n) const
    {
        return n < sz_ ? p_[n] : throw std::out_of_range("static_string");
    }
};

inline std::ostream& operator<<(std::ostream& os, static_string const& s)
{
    return os.write(s.data(), s.size());
}

/// \brief Get the name of a type
template <class T>
static_string typeName()
{
#ifdef __clang__
    static_string p = __PRETTY_FUNCTION__;
    return static_string(p.data() + 30, p.size() - 30 - 1);
#elif defined(_MSC_VER)
    static_string p = __FUNCSIG__;
    return static_string(p.data() + 37, p.size() - 37 - 7);
#endif

}

namespace details
{
    template <class Enum>
    struct EnumWrapper
    {
        template < Enum enu >
        static static_string name()
        {
#ifdef __clang__
            static_string p = __PRETTY_FUNCTION__;
            static_string enumType = typeName<Enum>();
            return static_string(p.data() + 73 + enumType.size(), p.size() - 73 - enumType.size() - 1);
#elif defined(_MSC_VER)
            static_string p = __FUNCSIG__;
            static_string enumType = typeName<Enum>();
            return static_string(p.data() + 57 + enumType.size(), p.size() - 57 - enumType.size() - 7);
#endif
        }
    };
}

/// \brief Get the name of an enum value
template <typename Enum, Enum enu>
static_string enumName()
{
    return details::EnumWrapper<Enum>::template name<enu>();
}

enum class Color
{
    Blue = 0,
    Yellow = 1
};


int main() 
{
    std::cout << "_" << typeName<Color>() << "_"  << std::endl;
    std::cout << "_" << enumName<Color, Color::Blue>() << "_"  << std::endl;
    return 0;
}

Yukarıdaki kod yalnızca Clang (bkz. Https://ideone.com/je5Quv ) ve VS2015 üzerinde test edilmiştir , ancak tamsayı sabitleriyle biraz uğraşarak diğer derleyicilere uyarlanabilir olmalıdır. Tabii ki, hala kaputun altında makrolar kullanıyor, ancak en azından birinin numaralandırma uygulamasına erişmesine gerek yok.


Bu, g ++ 6.3.0 ve C ++ 14 ile başarısız olur.
einpoklum

İlginçtir çünkü enum bir makroya sarmak zorunda kalmadan normal olarak bildirilebilir. Derleyici bağımlılıklarını ve sihirli sabitleri sevmiyorum.
zett42

2

Fikri @antron'dan aldım ve farklı uyguladım: gerçek bir numaralandırma sınıfı oluşturmak .

Bu uygulama, orijinal soruda listelenen tüm gereksinimleri karşılamaktadır, ancak şu anda yalnızca bir tane gerçek sınırlama vardır : numaralandırma değerlerinin sağlanmadığını veya sağlandıysa 0 ile başlayıp aralıksız olarak artması gerektiğini varsayar.

Bu gerçek bir sınırlama değildir - basitçe geçici enum değerleri kullanmıyorum. Bu gerekliyse, vektör aramasını geleneksel anahtar / vaka uygulamasıyla değiştirebilirsiniz.

Çözüm, satır içi değişkenler için bazı c ++ 17 kullanır, ancak gerekirse bu önlenebilir. Ayrıca boost kullanır : trim basitlik nedeniyle .

En önemlisi, sadece 30 satır kod alır ve kara büyü makroları içermez. Kod aşağıda. Başlığa konulmalı ve çoklu derleme modüllerine dahil edilmelidir.

Bu başlıkta daha önce önerildiği şekilde kullanılabilir:

ENUM(Channel, int, Red, Green = 1, Blue)
std::out << "My name is " << Channel::Green;
//prints My name is Green

Pls bana bu yararlı olup olmadığını ve daha da geliştirilebilir bildirin.


#include <boost/algorithm/string.hpp>   
struct EnumSupportBase {
  static std::vector<std::string> split(const std::string s, char delim) {
    std::stringstream ss(s);
    std::string item;
    std::vector<std::string> tokens;
    while (std::getline(ss, item, delim)) {
        auto pos = item.find_first_of ('=');
        if (pos != std::string::npos)
            item.erase (pos);
        boost::trim (item);
        tokens.push_back(item);
    }
    return tokens;
  }
};
#define ENUM(EnumName, Underlying, ...) \
    enum class EnumName : Underlying { __VA_ARGS__, _count }; \
    struct EnumName ## Support : EnumSupportBase { \
        static inline std::vector<std::string> _token_names = split(#__VA_ARGS__, ','); \
        static constexpr const char* get_name(EnumName enum_value) { \
            int index = (int)enum_value; \
            if (index >= (int)EnumName::_count || index < 0) \
               return "???"; \
            else \
               return _token_names[index].c_str(); \
        } \
    }; \
    inline std::ostream& operator<<(std::ostream& os, const EnumName & es) { \
        return os << EnumName##Support::get_name(es); \
    } 

2

.h/.cppHer sorgulanabilir numaralandırma için ayrı bir çift yazma konusunda sorun yaşamadığınız sürece, bu çözüm normal bir c ++ numaralandırmasıyla neredeyse aynı sözdizimi ve özelliklerle çalışır:

// MyEnum.h
#include <EnumTraits.h>
#ifndef ENUM_INCLUDE_MULTI
#pragma once
#end if

enum MyEnum : int ETRAITS
{
    EDECL(AAA) = -8,
    EDECL(BBB) = '8',
    EDECL(CCC) = AAA + BBB
};

.cppDosya Demirbaş 3 satır:

// MyEnum.cpp
#define ENUM_DEFINE MyEnum
#define ENUM_INCLUDE <MyEnum.h>
#include <EnumTraits.inl>

Örnek kullanım:

for (MyEnum value : EnumTraits<MyEnum>::GetValues())
    std::cout << EnumTraits<MyEnum>::GetName(value) << std::endl;

kod

Bu çözüm 2 kaynak dosyası gerektirir:

// EnumTraits.h
#pragma once
#include <string>
#include <unordered_map>
#include <vector>

#define ETRAITS
#define EDECL(x) x

template <class ENUM>
class EnumTraits
{
public:
    static const std::vector<ENUM>& GetValues()
    {
        return values;
    }

    static ENUM GetValue(const char* name)
    {
        auto match = valueMap.find(name);
        return (match == valueMap.end() ? ENUM() : match->second);
    }

    static const char* GetName(ENUM value)
    {
        auto match = nameMap.find(value);
        return (match == nameMap.end() ? nullptr : match->second);
    }

public:
    EnumTraits() = delete;

    using vector_type = std::vector<ENUM>;
    using name_map_type = std::unordered_map<ENUM, const char*>;
    using value_map_type = std::unordered_map<std::string, ENUM>;

private:
    static const vector_type values;
    static const name_map_type nameMap;
    static const value_map_type valueMap;
};

struct EnumInitGuard{ constexpr const EnumInitGuard& operator=(int) const { return *this; } };
template <class T> constexpr T& operator<<=(T&& x, const EnumInitGuard&) { return x; }

...ve

// EnumTraits.inl
#define ENUM_INCLUDE_MULTI

#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

using EnumType = ENUM_DEFINE;
using TraitsType = EnumTraits<EnumType>;
using VectorType = typename TraitsType::vector_type;
using NameMapType = typename TraitsType::name_map_type;
using ValueMapType = typename TraitsType::value_map_type;
using NamePairType = typename NameMapType::value_type;
using ValuePairType = typename ValueMapType::value_type;

#define ETRAITS ; const VectorType TraitsType::values
#define EDECL(x) EnumType::x <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

#define ETRAITS ; const NameMapType TraitsType::nameMap
#define EDECL(x) NamePairType(EnumType::x, #x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

#define ETRAITS ; const ValueMapType TraitsType::valueMap
#define EDECL(x) ValuePairType(#x, EnumType::x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

açıklama

Bu uygulama, bir enum tanımının hazır öğelerinin hazır listesinin, sınıf üyesi başlatma için hazır bir başlangıç ​​listesi olarak da kullanılabileceğinden yararlanır.

Zaman ETRAITSiçinde kabul edilir EnumTraits.inl, bu için statik elemanı tanımına genleşerek dışarıEnumTraits<> sınıfı.

EDECLSonradan sırayla üye yapıcı içine geçmiş olsun başlatıcısı liste değerlere makro dönüşümler her numaralama üyesi enum bilgi doldurmak için.

EnumInitGuardEnum veri saf listesini ayrılan - sınıf daraltmak sonra değerleri başlatıcı enum tüketir ve tasarlanmıştır.

Yararları

  • c++benzeri sözdizimi
  • Her ikisi için de aynı şekilde çalışır enumve enum class(* neredeyse)
  • enumAltta yatan sayısal türden türler için çalışır
  • enumOtomatik, açık ve parçalanmış başlatıcı değerlerine sahip türler için çalışır
  • Toplu yeniden adlandırma çalışmaları (akıllı bağlantı)
  • Yalnızca 5 önişlemci simgesi (3 genel)

* Aksine , aynı numaradan diğer değerlere gönderme enumsyapan enum classtürdeki başlatıcılar , bu değerlere tam olarak sahip olmalıdır

Disbenefits

  • .h/.cppHer sorgulanabilir için ayrı bir çift gerektirirenum
  • Kıvrık macrove includesihire bağlıdır
  • Küçük sözdizimi hataları çok daha büyük hatalara dönüşür
  • Numaralandırma classveya namespacekapsam belirleme önemsiz değildir
  • Derleme zamanı başlatma yok

Yorumlar

Intellisense, açılırken özel üye erişiminden biraz şikayet edecek EnumTraits.inl, ancak genişletilmiş makrolar aslında sınıf üyelerini tanımladığından, bu aslında bir sorun değil.

#ifndef ENUM_INCLUDE_MULTIBaşlık dosyasının en üstünde blok muhtemelen bir makro veya bir şeye aşağı çekmiş olabilir küçük bir sıkıntı, ama geçerli boyutunda yaşamak küçük yeterli.

Bir ad alanı kapsamındaki numaralandırma bildirimi için, enumun önce ad alanı kapsamı içinde ileri doğru bildirilmesi, sonra genel ad alanında tanımlanması gerekir. Ayrıca, aynı numaralandırmanın değerlerini kullanan tüm numaralandırıcılar bu değerlere tam olarak sahip olmalıdır.

namespace ns { enum MyEnum : int; }
enum ns::MyEnum : int ETRAITS
{
    EDECL(AAA) = -8,
    EDECL(BBB) = '8',
    EDECL(CCC) = ns::MyEnum::AAA + ns::MyEnum::BBB
}

2

Bu yaklaşımın zaten diğer cevaplardan birinde olup olmadığından emin değilim (aslında, aşağıya bakınız). Sorunla birçok kez karşılaştım ve gizli makroları veya üçüncü taraf kütüphanelerini kullanmayan bir çözüm bulamadım. Bu yüzden kendi gizlenmiş makro versiyonumu yazmaya karar verdim.

Etkinleştirmek istediğim şey

enum class test1 { ONE, TWO = 13, SIX };

std::string toString(const test1& e) { ... }

int main() {
    test1 x;
    std::cout << toString(x) << "\n";
    std::cout << toString(test1::TWO) << "\n";
    std::cout << static_cast<std::underlying_type<test1>::type>(test1::TWO) << "\n";
    //std::cout << toString(123);// invalid
}

hangisi yazdırmalı

ONE
TWO
13

Ben makro hayranı değilim. Ancak, c ++ yerel dizeleri dönüştürmek desteklemiyor sürece bir tür kod üretimi ve / veya makroları kullanmak zorunda (ve bu çok yakında olacak şüpheliyim). Bir X-makro kullanıyorum :

// x_enum.h
#include <string>
#include <map>
#include <type_traits>
#define x_begin enum class x_name {
#define x_val(X) X
#define x_value(X,Y) X = Y
#define x_end };
x_enum_def
#undef x_begin
#undef x_val
#undef x_value
#undef x_end

#define x_begin inline std::string toString(const x_name& e) { \
                static std::map<x_name,std::string> names = { 
#define x_val(X)      { x_name::X , #X }
#define x_value(X,Y)  { x_name::X , #X }
#define x_end }; return names[e]; }
x_enum_def
#undef x_begin
#undef x_val
#undef x_value
#undef x_end
#undef x_name
#undef x_enum_def

Çoğu, kullanıcının bir içerme yoluyla X-marco'ya parametre olarak ileteceği sembolleri tanımlamak ve tanımlamaktır. Kullanım böyle

#define x_name test1
#define x_enum_def x_begin x_val(ONE) , \
                           x_value(TWO,13) , \
                           x_val(SIX) \
                   x_end
#include "x_enum.h"

Canlı Demo

Henüz altta yatan türü seçme dahil etmediğini unutmayın. Şimdiye kadar ihtiyacım yoktu, ama bunu etkinleştirmek için kod değiştirmek için düz ileri olmalıdır.

Sadece bunu yazdıktan sonra bunun eferions cevabına oldukça benzediğini fark ettim . Belki daha önce okudum ve belki de ana ilham kaynağıydı. Kendimi yazana kadar X-makrolarını anlama konusunda başarısız oluyordum;).


1

Sınıf / yapı içinde enum kullanan çözümler (genel üyelerle yapısal varsayılanlar) ve aşırı yüklenmiş işleçler:

struct Color
{
    enum Enum { RED, GREEN, BLUE };
    Enum e;

    Color() {}
    Color(Enum e) : e(e) {}

    Color operator=(Enum o) { e = o; return *this; }
    Color operator=(Color o) { e = o.e; return *this; }
    bool operator==(Enum o) { return e == o; }
    bool operator==(Color o) { return e == o.e; }
    operator Enum() const { return e; }

    std::string toString() const
    {
        switch (e)
        {
        case Color::RED:
            return "red";
        case Color::GREEN:
            return "green";
        case Color::BLUE:
            return "blue";
        default:
            return "unknown";
        }
    }
};

Dışarıdan neredeyse bir sınıf enumuna benziyor:

Color red;
red = Color::RED;
Color blue = Color::BLUE;

cout << red.toString() << " " << Color::GREEN << " " << blue << endl;

Bu "kırmızı 1 2" çıkacaktır. Mavi çıktıyı bir dize yapmak için << aşırı yükleyebilirsiniz (belirsizliğe neden olmamasına rağmen), ancak otomatik olarak Renge dönüşmediği için Color :: GREEN ile çalışmaz.

Enum'a (dolaylı olarak int veya tipe dolaylı olarak dönüşen) örtük bir dönüştürme yapmanın amacı şunları yapabilmektir:

Color color;
switch (color) ...

Bu işe yarıyor, ama aynı zamanda bunun da işe yaradığı anlamına geliyor:

int i = color;

Bir enum sınıfı ile derlenmeyecekti. Numaralandırma ve bir tamsayı alan iki işlevi aşırı yüklerseniz veya örtük dönüşümü kaldırırsanız dikkatli olmalısınız ...

Başka bir çözüm, gerçek bir enum sınıfı ve statik üyelerin kullanılmasını içerir:

struct Color
{
    enum class Enum { RED, GREEN, BLUE };
    static const Enum RED = Enum::RED, GREEN = Enum::GREEN, BLUE = Enum::BLUE;

    //same as previous...
};

Muhtemelen daha fazla yer kaplar ve daha uzun sürer, ancak örtük int dönüşümler için derleme hatasına neden olur. Bu yüzden bunu kullanardım!

Kesinlikle bununla ek yük var, ama bence sadece daha basit ve gördüğüm diğer koddan daha iyi görünüyor. Ayrıca, sınıf dahilinde ele alınabilecek işlevsellik ekleme potansiyeli de vardır.

Düzenleme : bu çalışır ve çoğu yürütmeden önce derlenebilir:

class Color
{
public:
    enum class Enum { RED, GREEN, BLUE };
    static const Enum RED = Enum::RED, GREEN = Enum::GREEN, BLUE = Enum::BLUE;

    constexpr Color() : e(Enum::RED) {}
    constexpr Color(Enum e) : e(e) {}

    constexpr bool operator==(Enum o) const { return e == o; }
    constexpr bool operator==(Color o) const { return e == o.e; }
    constexpr operator Enum() const { return e; }

    Color& operator=(Enum o) { const_cast<Enum>(this->e) = o; return *this; }
    Color& operator=(Color o) { const_cast<Enum>(this->e) = o.e; return *this; }

    std::string toString() const
    {
        switch (e)
        {
        case Enum::RED:
            return "red";
        case Enum::GREEN:
            return "green";
        case Enum::BLUE:
            return "blue";
        default:
            return "unknown";
        }
    }
private:
    const Enum e;
};

Bu çok ilginç :-) Ancak şu anki sürümünüz, el ile bir şeyler yazmanız gerektiği anlamına geliyor case Enum::RED: return "red";. Soru, bu bilgileri derleyici tarafından otomatikleştirmektir (derleme zamanında). Sorunun amacı, şeyleri güncellemek zorunda kalmadan yalnızca enum değerlerini değiştirmek veya eklemektir toString(). Görüyor musun? Teşekkürler
olibre

1

Büyük bir kısıtlamayla çok basit bir çözüm: değerlere özel değerler atayamazsınız enum, ancak doğru regex ile bunu yapabilirsiniz. enumdaha fazla çaba harcamadan değerlere geri çevirmek için bir harita da ekleyebilirsiniz :

#include <vector>
#include <string>
#include <regex>
#include <iterator>

std::vector<std::string> split(const std::string& s, 
                               const std::regex& delim = std::regex(",\\s*"))
{
    using namespace std;
    vector<string> cont;
    copy(regex_token_iterator<string::const_iterator>(s.begin(), s.end(), delim, -1), 
         regex_token_iterator<string::const_iterator>(),
         back_inserter(cont));
    return cont;
}

#define EnumType(Type, ...)     enum class Type { __VA_ARGS__ }

#define EnumStrings(Type, ...)  static const std::vector<std::string> \
                                Type##Strings = split(#__VA_ARGS__);

#define EnumToString(Type, ...) EnumType(Type, __VA_ARGS__); \
                                EnumStrings(Type, __VA_ARGS__)

Kullanım örneği:

EnumToString(MyEnum, Red, Green, Blue);

Yenilikçi fikriniz için teşekkür ederim Malem. Okunabilirliği artırmak için cevabınızı düzenledim. Umarım değişikliklerimi beğenirsiniz. Cevabınızı geliştirmeye devam edin: (1) bölümü genişletmek "Kullanım örneği" gibi bir şeyle auto name = MyEnumStrings["Red"];- (2) Neden kullanıyorsunuz enum class? - (3) Destekliyor enum class MyEnum : char { Red, Green, Blue };musunuz? - (4) Açıkla fonksiyonu split()- (5) Parametreye const std::regex& delimmi ihtiyacınız var ? - (6) MyEnumStringsDerleme zamanında üretmeye ne dersiniz ? => Kullanabilir misiniz constexpr? ... Şerefe :-)
olibre

Bu yaklaşımı gerçekten seviyorum. Gerçekten kısa ve anlaşılması kolay.
Anton Holmberg

1

EDIT: daha yeni bir sürüm için aşağıya bakın

Yukarıda belirtildiği gibi, N4113 bu konuda nihai çözümdür, ancak ortaya çıktığını görmek için bir yıldan fazla beklemek zorunda kalacağız.

Bu arada, böyle bir özellik istiyorsanız, "basit" şablonlara ve bazı önişlemci sihrine başvurmanız gerekir.

sayım memuru

template<typename T>
class Enum final
{
    const char* m_name;
    const T m_value;
    static T m_counter;

public:
    Enum(const char* str, T init = m_counter) : m_name(str), m_value(init) {m_counter = (init + 1);}

    const T value() const {return m_value;}
    const char* name() const {return m_name;}
};

template<typename T>
T Enum<T>::m_counter = 0;

#define ENUM_TYPE(x)      using Enum = Enum<x>;
#define ENUM_DECL(x,...)  x(#x,##__VA_ARGS__)
#define ENUM(...)         const Enum ENUM_DECL(__VA_ARGS__);

kullanım

#include <iostream>

//the initialization order should be correct in all scenarios
namespace Level
{
    ENUM_TYPE(std::uint8)
    ENUM(OFF)
    ENUM(SEVERE)
    ENUM(WARNING)
    ENUM(INFO, 10)
    ENUM(DEBUG)
    ENUM(ALL)
}

namespace Example
{
    ENUM_TYPE(long)
    ENUM(A)
    ENUM(B)
    ENUM(C, 20)
    ENUM(D)
    ENUM(E)
    ENUM(F)
}

int main(int argc, char** argv)
{
    Level::Enum lvl = Level::WARNING;
    Example::Enum ex = Example::C;
    std::cout << lvl.value() << std::endl; //2
    std::cout << ex.value() << std::endl; //20
}

Basit açıklama

Enum<T>::m_counterher ad alanı bildirimi içinde 0 olarak ayarlanır.
( Birisi bana standartta ^ ^ ^ ^ bu davranıştan ^ ^ bahsettiğini
belirtebilir mi? )

Dezavantajları

  • Bu gerçek bir enumtür değil, bu nedenle int için promotable değil
  • Anahtar kutularında kullanılamaz

Alternatif çözüm

Bu, satır numaralandırmayı feda eder (gerçekte değil), ancak anahtar durumlarında kullanılabilir .

#define ENUM_TYPE(x) using type = Enum<x>
#define ENUM(x)      constexpr type x{__LINE__,#x}

template<typename T>
struct Enum final
{
    const T value;
    const char* name;

    constexpr operator const T() const noexcept {return value;}
    constexpr const char* operator&() const noexcept {return name;}
};

Hatalar

#line 0 ile çatışmalar -pedanticGCC ve clang .

Geçici çözüm

Başlayın #line 1ve 1'den çıkartın __LINE__.
Ya da kullanma -pedantic.
Ve biz varken, her ne pahasına olursa olsun VC ++ kaçının, her zaman bir derleyicinin şakası olmuştur.

kullanım

#include <iostream>

namespace Level
{
    ENUM_TYPE(short);
    #line 0
    ENUM(OFF);
    ENUM(SEVERE);
    ENUM(WARNING);
    #line 10
    ENUM(INFO);
    ENUM(DEBUG);
    ENUM(ALL);
    #line <next line number> //restore the line numbering
};

int main(int argc, char** argv)
{
    std::cout << Level::OFF << std::endl;   // 0
    std::cout << &Level::OFF << std::endl;  // OFF

    std::cout << Level::INFO << std::endl;  // 10
    std::cout << &Level::INFO << std::endl; // INFO

    switch(/* any integer or integer-convertible type */)
    {
    case Level::OFF:
        //...
        break;

    case Level::SEVERE:
        //...
        break;

    //...
    }

    return 0;
}

Gerçek hayattaki uygulama ve kullanım

r3dVoxel - Enum
r3dVoxel - ELoggingLevel

Hızlı referans

#line lineno - cppreference.com


0

Bu sorunu çözmek için bir kütüphane yazdım, mesajı almak dışında her şey derleme zamanında oluyor.

Kullanımı:

Makro kullanın DEF_MSGmakro ve mesaj çiftini tanımlamak için:

DEF_MSG(CODE_OK,   "OK!")
DEF_MSG(CODE_FAIL, "Fail!")

CODE_OKkullanılacak makro ve "OK!"karşılık gelen mesajdır.

Mesajı almak için get_message()veya tuşunu kullanın gm():

get_message(CODE_FAIL);  // will return "Fail!"
gm(CODE_FAIL);           // works exactly the same as above

MSG_NUMKaç makro tanımlandığını bulmak için kullanın . Bu otomatik olarak artar, hiçbir şey yapmanıza gerek yoktur.

Önceden tanımlanmış mesajlar:

MSG_OK:     OK
MSG_BOTTOM: Message bottom

Proje: libcodemsg


Kütüphane fazladan veri oluşturmaz. Her şey zaman derlemesinde olur. Içinde message_def.h, bir enumçağrı üretir MSG_CODE; içinde message_def.c, tüm dizeleri tutan bir değişken oluşturur static const char* _g_messages[].

Bu durumda, kütüphane enumsadece bir tane oluşturmak için sınırlıdır . Bu, dönüş değerleri için idealdir, örneğin:

MSG_CODE foo(void) {
    return MSG_OK; // or something else
}

MSG_CODE ret = foo();

if (MSG_OK != ret) {
    printf("%s\n", gm(ret););
}

Bu tasarımı sevdiğim başka bir şey, mesaj tanımlarını farklı dosyalarda yönetebilmenizdir.


Bu sorunun çözümünün daha iyi göründüğünü buldum .


Merhaba Madwyn. Fikriniz için teşekkürler. Ama nasıl çalışır? Genel gider nedir? (ek yük sıfır mı yoksa ekstra veri mi oluşturuyor?). Teklifiniz iyi görünüyor, ancak ne yazık ki, DEF_MSGher enumdeğer için bir ifade kullanılmalı / güncellenmeli / korunmalıdır : - / Ve ideal olarak bunu yapmaktan vazgeçmek istiyoruz ... Şerefe
olibre

Cevabınız için teşekkür ederim, @olibre. Lütfen güncellenmiş cevabı kontrol edin. Buraya ek yük görmüyorum, ancak dizelere erişmek için bir işlev çağrısı gerekiyor. DEF_MSGyapar enumbazı sınırlamaları vardır, ancak yakından mesajla eşleştirilmiş.
Madwyn

Cevabınızdaki ek açıklama için teşekkür ederim :-) Lib'iniz iyi ancak birden fazla numaralandırma için kullanılamaz: - / enum class(C ++ 11) desteği ne olacak ? Çalışma zamanında constexprsınırlamak _g_messagesiçin kullanabilirsiniz . Meta-programlama (tip taşıma {enum-tipi, enum-değer}) veya belki de şablon değişkenleri (C ++ 14) kullanarak çoklu enumtürleri (kaçınarak ) destekleyin . Bence lib'niz (henüz?) C ++ 11/14/17 şartlarına uymuyor. Ne düşünüyorsun? Şerefe ;-)_g_messages
olibre

1
Takip ettiğiniz için teşekkürler. Bugün yeni bir şey öğrendim! Enum sınıfı ve şablon değişkenleri iyi görünüyor. C aromalı olduğu gibi benim cevap biraz "konu dışı" olduğunu düşünüyorum.
Madwyn

0
#define ENUM_MAKE(TYPE, ...) \
        enum class TYPE {__VA_ARGS__};\
        struct Helper_ ## TYPE { \
            static const String& toName(TYPE type) {\
                int index = static_cast<int>(type);\
                return splitStringVec()[index];}\
            static const TYPE toType(const String& name){\
                static std::unordered_map<String,TYPE> typeNameMap;\
                if( typeNameMap.empty() )\
                {\
                    const StringVector& ssVec = splitStringVec();\
                    for (size_t i = 0; i < ssVec.size(); ++i)\
                        typeNameMap.insert(std::make_pair(ssVec[i], static_cast<TYPE>(i)));\
                }\
                return typeNameMap[name];}\
            static const StringVector& splitStringVec() {\
                static StringVector typeNameVector;\
                if(typeNameVector.empty()) \
                {\
                    typeNameVector = StringUtil::split(#__VA_ARGS__, ",");\
                    for (auto& name : typeNameVector)\
                    {\
                        name.erase(std::remove(name.begin(), name.end(), ' '),name.end()); \
                        name = String(#TYPE) + "::" + name;\
                    }\
                }\
                return typeNameVector;\
            }\
        };


using String = std::string;
using StringVector = std::vector<String>;

   StringVector StringUtil::split( const String& str, const String& delims, unsigned int maxSplits, bool preserveDelims)
    {
        StringVector ret;
        // Pre-allocate some space for performance
        ret.reserve(maxSplits ? maxSplits+1 : 10);    // 10 is guessed capacity for most case

        unsigned int numSplits = 0;

        // Use STL methods 
        size_t start, pos;
        start = 0;
        do 
        {
            pos = str.find_first_of(delims, start);
            if (pos == start)
            {
                // Do nothing
                start = pos + 1;
            }
            else if (pos == String::npos || (maxSplits && numSplits == maxSplits))
            {
                // Copy the rest of the string
                ret.push_back( str.substr(start) );
                break;
            }
            else
            {
                // Copy up to delimiter
                ret.push_back( str.substr(start, pos - start) );

                if(preserveDelims)
                {
                    // Sometimes there could be more than one delimiter in a row.
                    // Loop until we don't find any more delims
                    size_t delimStart = pos, delimPos;
                    delimPos = str.find_first_not_of(delims, delimStart);
                    if (delimPos == String::npos)
                    {
                        // Copy the rest of the string
                        ret.push_back( str.substr(delimStart) );
                    }
                    else
                    {
                        ret.push_back( str.substr(delimStart, delimPos - delimStart) );
                    }
                }

                start = pos + 1;
            }
            // parse up to next real data
            start = str.find_first_not_of(delims, start);
            ++numSplits;

        } while (pos != String::npos);



        return ret;
    }

misal

ENUM_MAKE(MY_TEST, MY_1, MY_2, MY_3)


    MY_TEST s1 = MY_TEST::MY_1;
    MY_TEST s2 = MY_TEST::MY_2;
    MY_TEST s3 = MY_TEST::MY_3;

    String z1 = Helper_MY_TEST::toName(s1);
    String z2 = Helper_MY_TEST::toName(s2);
    String z3 = Helper_MY_TEST::toName(s3);

    MY_TEST q1 = Helper_MY_TEST::toType(z1);
    MY_TEST q2 = Helper_MY_TEST::toType(z2);
    MY_TEST q3 = Helper_MY_TEST::toType(z3);

otomatik olarak ENUM_MAKE makrosu 'enum class' ve 'enum yansıma fonksiyonu' ile yardımcı sınıf üretir.

Hataları azaltmak için, bir kerede Her şey yalnızca bir ENUM_MAKE ile tanımlanır.

Bu kodun avantajı, yansıma ve makro koduna, anlaşılması kolay koda yakından bakmak için otomatik olarak oluşturulur. 'enum to string', 'string to enum' performansı her ikisi de O (1) algoritmasıdır.

Dezavantajları ilk kullanımda, enum relection 's string vektörü ve haritası için yardımcı sınıf başlatıldığı zamandır. ancak isterseniz, önceden kullanıma hazırlanırsınız. -


Bu kod soruyu cevaplayabilirken, başkalarını tanıtmadan sorunun nasıl çözüldüğünü ve neden kullanıldığını açıklamak daha iyi olacaktır. Yalnızca kod yanıtları uzun vadede yararlı değildir.
JAL

hey millet, üzgünüm ingilizce çok iyi konuşamıyorum.
desperado_98

otomatik olarak ENUM_MAKE makrosu 'enum class' ve 'enum yansıma fonksiyonu' ile yardımcı sınıf üretir. / Hataları azaltmak için, bir kerede Her şey yalnızca bir ENUM_MAKE ile tanımlanır. Bu kodun avantajı, yansıma ve makro koduna, anlaşılması kolay koda yakından bakmak için otomatik olarak oluşturulur. 'enum to string', 'string to enum' performansı her ikisi de O (1) algoritmasıdır. Dezavantajları ilk kullanımda, enum relection 's string vektörü ve haritası için yardımcı sınıf başlatıldığı zamandır. ancak isterseniz, önceden kullanıma hazırlanırsınız.
desperado_98

Merhaba desperado_98. Katkınız için teşekkürler. Lütfen cevabınızı düzenleyin ve içine yorum içeriğinizi ekleyin. Bazı meta programlama hileleri kullanırsanız, derleyici örneğinizi derleme zamanında hesaplayabilir constexpr. Fonksiyonları kastediyorum toName()ve toType()derleme sırasında ve yürütme sırasında (çalışma zamanı) değerlendirilebilir. Lütfen cevabınızda C ++ 11/14/17 stilini benimseyin. Şerefe ;-)
olibre

Dahası: Makronuz ile uyumlu enum class MyEnum : short { A, B, C };mu?
olibre

0

benim çözümüm makro kullanmadan.

avantajları:

  • tam olarak ne yaptığını görüyorsun
  • erişim karma haritalarla yapılır, bu nedenle birçok değerli numaralandırma için iyi
  • siparişi veya ardışık olmayan değerleri dikkate almaya gerek yok
  • hem enum to string, hem de string enum translation olmakla birlikte, eklenen enum değeri yalnızca ek bir yere eklenmelidir

Dezavantajları:

  • tüm numaralandırma değerlerini metin olarak çoğaltmanız gerekir
  • karma haritaya erişim string case'i dikkate almalıdır
  • değer eklemek acı verici ise bakım - hem enum hem de doğrudan tercüme haritasına eklemelisiniz

yani ... C ++ C # Enum.Parse işlevselliğini uygulayan güne kadar, ben bu sıkışmış olacak:

            #include <unordered_map>

            enum class Language
            { unknown, 
                Chinese, 
                English, 
                French, 
                German
                // etc etc
            };

            class Enumerations
            {
            public:
                static void fnInit(void);

                static std::unordered_map <std::wstring, Language> m_Language;
                static std::unordered_map <Language, std::wstring> m_invLanguage;

            private:
                static void fnClear();
                static void fnSetValues(void);
                static void fnInvertValues(void);

                static bool m_init_done;
            };

            std::unordered_map <std::wstring, Language> Enumerations::m_Language = std::unordered_map <std::wstring, Language>();
            std::unordered_map <Language, std::wstring> Enumerations::m_invLanguage = std::unordered_map <Language, std::wstring>();

            void Enumerations::fnInit()
            {
                fnClear();
                fnSetValues();
                fnInvertValues();
            }

            void Enumerations::fnClear()
            {
                m_Language.clear();
                m_invLanguage.clear();
            }

            void Enumerations::fnSetValues(void)
            {   
                m_Language[L"unknown"] = Language::unknown;
                m_Language[L"Chinese"] = Language::Chinese;
                m_Language[L"English"] = Language::English;
                m_Language[L"French"] = Language::French;
                m_Language[L"German"] = Language::German;
                // and more etc etc
            }

            void Enumerations::fnInvertValues(void)
            {
                for (auto it = m_Language.begin(); it != m_Language.end(); it++)
                {
                    m_invLanguage[it->second] = it->first;
                }
            }

            // usage -
            //Language aLanguage = Language::English;
            //wstring sLanguage = Enumerations::m_invLanguage[aLanguage];

            //wstring sLanguage = L"French" ;
            //Language aLanguage = Enumerations::m_Language[sLanguage];

0

Peki, yine başka bir seçenek. Tipik bir kullanım durumu, HTTP fiilleri için sabitlerin yanı sıra dize sürüm değerlerini kullanmanız gerektiğidir.

Örnek:

int main () {

  VERB a = VERB::GET;
  VERB b = VERB::GET;
  VERB c = VERB::POST;
  VERB d = VERB::PUT;
  VERB e = VERB::DELETE;


  std::cout << a.toString() << std::endl;

  std::cout << a << std::endl;

  if ( a == VERB::GET ) {
    std::cout << "yes" << std::endl;
  }

  if ( a == b ) {
    std::cout << "yes" << std::endl;
  }

  if ( a != c ) {
    std::cout << "no" << std::endl;
  }

}

VERB sınıfı:

// -----------------------------------------------------------
// -----------------------------------------------------------
class VERB {

private:

  // private constants
  enum Verb {GET_=0, POST_, PUT_, DELETE_};

  // private string values
  static const std::string theStrings[];

  // private value
  const Verb value;
  const std::string text;

  // private constructor
  VERB (Verb v) :
  value(v), text (theStrings[v])
  {
    // std::cout << " constructor \n";
  }

public:

  operator const char * ()  const { return text.c_str(); }

  operator const std::string ()  const { return text; }

  const std::string toString () const { return text; }

  bool operator == (const VERB & other) const { return (*this).value == other.value; }

  bool operator != (const VERB & other) const { return ! ( (*this) == other); }

  // ---

  static const VERB GET;
  static const VERB POST;
  static const VERB PUT;
  static const VERB DELETE;

};

const std::string VERB::theStrings[] = {"GET", "POST", "PUT", "DELETE"};

const VERB VERB::GET = VERB ( VERB::Verb::GET_ );
const VERB VERB::POST = VERB ( VERB::Verb::POST_ );
const VERB VERB::PUT = VERB ( VERB::Verb::PUT_ );
const VERB VERB::DELETE = VERB ( VERB::Verb::DELETE_ );
// end of file

1
Bellek kullanımını azaltmak için üyeyi const std::string textsadece değiştirebilirsiniz theStrings[v]. / -: Ancak soru ++ 11 / C ++ 14 / C ++ 17 / C ++ 20 elle, örneğin sınıf yazmak zorunda kalmamak için C özellikleri ile ilgilidir
olibre

0

Cevabım burada.

Enum değeri adlarını ve bu dizinleri eşzamanlı olarak dize aykırı olarak alabilirsiniz.

Bu yöntem yalnızca çok az kopyala yapıştır ve düzenlemeye ihtiyaç duyar.

Elde edilen sonuç, enum sınıfı türü değerine ihtiyacınız olduğunda size_t'den enum sınıfı türüne döküm türüne ihtiyaç duyar, ancak enum sınıfını tedavi etmenin çok taşınabilir ve güçlü bir yol olduğunu düşünüyorum.

enum class myenum
{
  one = 0,
  two,
  three,
};

deque<string> ssplit(const string &_src, boost::regex &_re)
{
  boost::sregex_token_iterator it(_src.begin(), _src.end(), _re, -1);
  boost::sregex_token_iterator e;
  deque<string> tokens;
  while (it != e)
    tokens.push_back(*it++);
  return std::move(tokens);
}

int main()
{
  regex re(",");
  deque<string> tokens = ssplit("one,two,three", re);
  for (auto &t : tokens) cout << t << endl;
    getchar();
  return 0;
}

0

Ponder gibi bir yansıma kütüphanesi kullanabilirsiniz :

enum class MyEnum
{
    Zero = 0,
    One  = 1,
    Two  = 2
};

ponder::Enum::declare<MyEnum>()
    .value("Zero", MyEnum::Zero)
    .value("One",  MyEnum::One)
    .value("Two",  MyEnum::Two);

ponder::EnumObject zero(MyEnum::Zero);

zero.name(); // -> "Zero"

0

( Https://stackoverflow.com/a/54967187/2338477 , biraz değiştirilmiş analog ).

İşte asgari tanım sihri ve bireysel enum ödevlerini destekleyen kendi çözümüm.

İşte başlık dosyası:

#pragma once
#include <string>
#include <map>
#include <regex>

template <class Enum>
class EnumReflect
{
public:
    static const char* getEnums() { return ""; }
};

//
//  Just a container for each enumeration type.
//
template <class Enum>
class EnumReflectBase
{
public:
    static std::map<std::string, int> enum2int;
    static std::map<int, std::string> int2enum;

    static void EnsureEnumMapReady( const char* enumsInfo )
    {
        if (*enumsInfo == 0 || enum2int.size() != 0 )
            return;

        // Should be called once per each enumeration.
        std::string senumsInfo(enumsInfo);
        std::regex re("^([a-zA-Z_][a-zA-Z0-9_]+) *=? *([^,]*)(,|$) *");     // C++ identifier to optional " = <value>"
        std::smatch sm;
        int value = 0;

        for (; regex_search(senumsInfo, sm, re); senumsInfo = sm.suffix(), value++)
        {
            string enumName = sm[1].str();
            string enumValue = sm[2].str();

            if (enumValue.length() != 0)
                value = atoi(enumValue.c_str());

            enum2int[enumName] = value;
            int2enum[value] = enumName;
        }
    }
};

template <class Enum>
std::map<std::string, int> EnumReflectBase<Enum>::enum2int;

template <class Enum>
std::map<int, std::string> EnumReflectBase<Enum>::int2enum;


#define DECLARE_ENUM(name, ...)                                         \
    enum name { __VA_ARGS__ };                                          \
    template <>                                                         \
    class EnumReflect<##name>: public EnumReflectBase<##name> {         \
    public:                                                             \
        static const char* getEnums() { return #__VA_ARGS__; }          \
    };




/*
    Basic usage:

    Declare enumeration:

DECLARE_ENUM( enumName,

    enumValue1,
    enumValue2,
    enumValue3 = 5,

    // comment
    enumValue4
);

    Conversion logic:

    From enumeration to string:

        printf( EnumToString(enumValue3).c_str() );

    From string to enumeration:

       enumName value;

       if( !StringToEnum("enumValue4", value) )
            printf("Conversion failed...");
*/

//
//  Converts enumeration to string, if not found - empty string is returned.
//
template <class T>
std::string EnumToString(T t)
{
    EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
    auto& int2enum = EnumReflect<T>::int2enum;
    auto it = int2enum.find(t);

    if (it == int2enum.end())
        return "";

    return it->second;
}

//
//  Converts string to enumeration, if not found - false is returned.
//
template <class T>
bool StringToEnum(const char* enumName, T& t)
{
    EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
    auto& enum2int = EnumReflect<T>::enum2int;
    auto it = enum2int.find(enumName);

    if (it == enum2int.end())
        return false;

    t = (T) it->second;
    return true;
}

İşte örnek test uygulaması:

DECLARE_ENUM(TestEnum,
    ValueOne,
    ValueTwo,
    ValueThree = 5,
    ValueFour = 7
);

DECLARE_ENUM(TestEnum2,
    ValueOne2 = -1,
    ValueTwo2,
    ValueThree2 = -4,
    ValueFour2
);

void main(void)
{
    string sName1 = EnumToString(ValueOne);
    string sName2 = EnumToString(ValueTwo);
    string sName3 = EnumToString(ValueThree);
    string sName4 = EnumToString(ValueFour);

    TestEnum t1, t2, t3, t4, t5 = ValueOne;
    bool b1 = StringToEnum(sName1.c_str(), t1);
    bool b2 = StringToEnum(sName2.c_str(), t2);
    bool b3 = StringToEnum(sName3.c_str(), t3);
    bool b4 = StringToEnum(sName4.c_str(), t4);
    bool b5 = StringToEnum("Unknown", t5);

    string sName2_1 = EnumToString(ValueOne2);
    string sName2_2 = EnumToString(ValueTwo2);
    string sName2_3 = EnumToString(ValueThree2);
    string sName2_4 = EnumToString(ValueFour2);

    TestEnum2 t2_1, t2_2, t2_3, t2_4, t2_5 = ValueOne2;
    bool b2_1 = StringToEnum(sName2_1.c_str(), t2_1);
    bool b2_2 = StringToEnum(sName2_2.c_str(), t2_2);
    bool b2_3 = StringToEnum(sName2_3.c_str(), t2_3);
    bool b2_4 = StringToEnum(sName2_4.c_str(), t2_4);
    bool b2_5 = StringToEnum("Unknown", t2_5);

Aynı başlık dosyasının güncellenmiş sürümü burada tutulacaktır:

https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/EnumReflect.h


-5

Basit bir akış aşırı yüklemesine ne dersiniz? Eğer biraz makro sihir yapmak istemiyorsanız hala haritalamayı sürdürmeniz gerekiyor, ancak orijinal çözümünüzden daha temiz buluyorum.

#include <cstdint>  // for std::uint_fast8_t
#include <array>
#include <string>
#include <iostream>

enum class MyEnum : std::uint_fast8_t {
   AAA,
   BBB,
   CCC,
};

std::ostream& operator<<(std::ostream& str, MyEnum type)
{
    switch(type)
    {
    case MyEnum::AAA: str << "AAA"; break;
    case MyEnum::BBB: str << "BBB"; break;
    case MyEnum::CCC: str << "CCC"; break;
    default: break;
    }
    return str;
}

int main()
{
   std::cout << MyEnum::AAA <<'\n';
}

5
1) daha fazla çoğaltma oluşturur 2) sizi akışları kullanmaya zorlar
Karoly Horvath

6
-1 Üzgünüm @ dau_sama ama tüm bu numaralandırmanın yinelenen soruları dizmeye amacı , numaralandırma / dize eşlemesinin bakımını önlemektir. Cevabınızın amaca uygun olmadığını düşünüyorsanız, lütfen cevabı silmeyi düşünün. Bir sonraki cevabınızda iyi şanslar;) Şerefe
olibre

-9

En kolay yol?
Ada kullanın: Enumeration'Image( Value )tam olarak istediğinizi yapar. C ++ 'a gerçekten ihtiyacınız varsa , işlevi dışa aktarmayı deneyebilirsiniz:

Function To_String( Input : Enumeration ) return Interfaces.C.Strings.chars_ptr is
    ( Interfaces.C.Strings.New_String( Enumeration'Image(Input) ) )
    with Export, Convention => C;

4
Bu soruya nasıl cevap veriyor? Soru, modern C ++ 'da bir enum'un dizeye dönüştürülmesini açıkça belirtmektedir.
Michael Choi

1
@MichaelChoi - Öyle ama iş için uygun aracını kullanarak sorunu da var: C ++ gelmez bütün çözülebilir sorunları çözebilir nedenle tamamlama turing ve sırf DEĞİL hızlı, sürdürülebilir veya verimli: Çözüm olduğu anlamına gelir. Doğru / İstenen fonksiyonu bulunan bir dil kullanmak ve bunu ihraç olan geçerli bir çözüm.
Shark8

3
Sorunun ilk cümlesinde "bu soru yeni C ++ özelliklerini kullanmakla ilgilidir". sonra "C ++ 11, C ++ 14 veya C ++ 17 yeni özellikleri kullanarak henüz [zarif bir yol bulamadım". Yazar açıkça bir C ++ çözümü arıyordu; Ada'da bir çözüm verdiniz, bu yüzden soruyu cevaplamadınız. Muhtemelen sorular kapsamında olmayan bir şeyi çözmek için tamamen farklı bir bağımlılık eklemenizi öneriyorsunuz.
Michael Choi
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.