enum class
От: Videoman Россия https://hts.tv/
Дата: 26.05.18 12:26
Оценка:
В С++ есть замечательная возможность объявить класс перечисления. При этом, мы защищаемся от случайного перехода от целых к перечислениям и обратно. Тут все понятно.
Теперь, в некоторых перечислениях, хотелось бы иметь логические операции |,|=,&,&=,~ и т.д. для работы с флагами. Это тоже без проблем можно сделать, перегрузить операторы и т.д.
Теперь, хотелось бы избавиться от рутины и добавить для всего этого шаблоны, но по некому условию, только если надо, иначе, шаблон для всех перечислений очень "жадная" штука и применяется вообще везде, где надо и не надою.
Есть подход в лоб для этого и он много где описан, в том числе на StackOverflow, типа такого:
template<typename Enum>  
struct EnableBitMaskOperators  
{
    static const bool enable = false;
};

template<typename Enum>  
typename std::enable_if<EnableBitMaskOperators<Enum>::enable, Enum>::type  
operator |(Enum lhs, Enum rhs)  
{
    using underlying = typename std::underlying_type<Enum>::type;
    return static_cast<Enum> (
        static_cast<underlying>(lhs) |
        static_cast<underlying>(rhs)
    );
}

Соответственно где нужно можно написать:
template<>  
struct EnableBitMaskOperators<MyEnum>  
{
    static const bool enable = true;
};

и все работает без проблем.

Теперь что не устраивает и что хотелось бы получить:
1. Данных подход очень не удобен в случае если у нас множество вложенных наймспейсов. Каждый раз приходится "лесенкой" выходит их них, а потом объявлять шаблон полностью квалифицируя именя. Типа:
//...
} // namespace n1
} // namespace n2

template<>  
struct EnableBitMaskOperators<n1:n2::MyEnum>  
{
    static const bool enable = true;
};

namespace n1
{
namespace n2
{
//...

2. У нас флаги очень часто используются внутри классов. В этом случае все еще сильнее усложняется.
3. Для флагов в защищенных секциях данный подход вообще не работает.
4. Необходимо не использовать макросы.

В общем, кто как решает данную проблему и что можете посоветовать?
Re: enum class
От: kov_serg Россия  
Дата: 26.05.18 13:04
Оценка:
Здравствуйте, Videoman, Вы писали:

V>В С++ есть замечательная возможность объявить класс перечисления.


V>1. Данных подход очень не удобен в случае если у нас множество вложенных наймспейсов. Каждый раз приходится "лесенкой" выходит их них, а потом объявлять шаблон полностью квалифицируя именя.

V>2. У нас флаги очень часто используются внутри классов. В этом случае все еще сильнее усложняется.
V>3. Для флагов в защищенных секциях данный подход вообще не работает.
V>4. Необходимо не использовать макросы.
Сами себе создаёте проблемы на ровном месте.

V>В общем, кто как решает данную проблему и что можете посоветовать?

struct MyFlags { enum { F1=1,F2=2,F3=4 }; };

struct A : MyFlags {
  void fn(int flags) { if (flags&(F1|F3)) { ... } }
};

...
A().fn(A::F2 | A::F3);

Для совсем больных можно так
  output
struct Flags {
    enum {
        ENABLE=1,    // B0
        SUFFIX=2,    // B1
        CACHED=4,    // B2
        OPCODE=0xF8,    // B7-B3 len=5
        COMPLEX_FIELD1=0xA01C0000    // B31,B29,B20-B18 len=5
    };
    int value;
    int enable() const { return value&ENABLE; }
    int suffix() const { return (value&SUFFIX)>>1; }
    int cached() const { return (value&CACHED)>>2; }
    int opcode() const { return (value&OPCODE)>>3; }
    int complex_field1() const { return ((value>>27)&0x10)|((value>>26)&8)|((value>>18)&7); }
    Flags& clear() { value=0; return *this; }
    Flags& enable(int v) { value=(value&~ENABLE)|(v&ENABLE); return *this; }
    Flags& suffix(int v) { value=(value&~SUFFIX)|((v<<1)&SUFFIX); return *this; }
    Flags& cached(int v) { value=(value&~CACHED)|((v<<2)&CACHED); return *this; }
    Flags& opcode(int v) { value=(value&~OPCODE)|((v<<3)&OPCODE); return *this; }
    Flags& complex_field1(int v) { value=(value&~COMPLEX_FIELD1)|(((v<<27)&0x80000000)|((v<<25)&0x20000000)|((v<<16)&0x1C0000)); return *this; }
    void examine(void (*op)(void* ctx,int mask,int value,const char* name),void *ctx) const {
        op(ctx,ENABLE,value&ENABLE,"ENABLE");
        op(ctx,SUFFIX,value&SUFFIX,"SUFFIX");
        op(ctx,CACHED,value&CACHED,"CACHED");
        op(ctx,OPCODE,value&OPCODE,"OPCODE");
        op(ctx,COMPLEX_FIELD1,value&COMPLEX_FIELD1,"COMPLEX_FIELD1");
    }
};
Re[2]: enum class
От: Videoman Россия https://hts.tv/
Дата: 26.05.18 13:32
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>Для совсем больных можно так


Мне не нужно для больных. Я выше описал что мне нужно.
Re: enum class
От: vopl Россия  
Дата: 26.05.18 13:36
Оценка: 1 (1)
Здравствуйте, Videoman, Вы писали:

V>Теперь что не устраивает и что хотелось бы получить:

V>1. Данных подход очень не удобен в случае если у нас множество вложенных наймспейсов. Каждый раз приходится "лесенкой" выходит их них, а потом объявлять шаблон полностью квалифицируя именя.
V>2. У нас флаги очень часто используются внутри классов. В этом случае все еще сильнее усложняется.
V>3. Для флагов в защищенных секциях данный подход вообще не работает.
V>4. Необходимо не использовать макросы.

V>В общем, кто как решает данную проблему и что можете посоветовать?


Просто нужен включатор другой природы. Вместо специализации шаблонного класса — использовать наличие/отсутствие свободной функции.

#include <type_traits>

//SFINAE по наличию включатора, ADL будет задействован
template<typename Enum, class=decltype(enableBitMaskOperators(*(Enum*)nullptr))>
Enum operator |(Enum lhs, Enum rhs)
{
    using underlying = typename std::underlying_type<Enum>::type;
    return static_cast<Enum> (
        static_cast<underlying>(lhs) |
        static_cast<underlying>(rhs)
    );
}

namespace ns1
{
    enum class Flags {a,b,c};
    void enableBitMaskOperators(Flags);//это включатор

    enum class NotFlags {a,b,c};
}

namespace ns2
{
    struct s
    {
    private:
        enum class Flags {a,b,c};
        friend void enableBitMaskOperators(Flags);// на самом деле это void ::ns2::enableBitMaskOperators(Flags);

        enum class NotFlags {a,b,c};


        void testUsage()
        {
            Flags f = Flags::a | Flags::b;
        }
    };
}

/////////0/////////1/////////2/////////3/////////4/////////5/////////6/////////7
int main()
{
    ns1::Flags f1 = ns1::Flags::a | ns1::Flags::b;
    //ns2::s::Flags f2 = ns2::s::Flags::a | ns2::s::Flags::b;

    //ns1::NotFlags nf1 = ns1::NotFlags::a | ns1::NotFlags::b;
    return 0;
}
Re[2]: enum class
От: Videoman Россия https://hts.tv/
Дата: 26.05.18 13:42
Оценка:
Здравствуйте, vopl, Вы писали:

V>Просто нужен включатор другой природы. Вместо специализации шаблонного класса — использовать наличие/отсутствие свободной функции.


Спасибо. Собственно, это а и делал. Кусочек задачи был в соседнем топике про ADL. В итоге это один из вариантов решения проблемы. Теперь вопрос, а можно ли поэлегантней?
Внутри класса и с приватной секцией этот подход не срабатывает.
Отредактировано 26.05.2018 13:44 Videoman . Предыдущая версия .
Re[3]: enum class
От: vopl Россия  
Дата: 26.05.18 13:51
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Внутри класса и с приватной секцией этот подход не срабатывает.



смотри ns2::s::Flags, все срабатывает
Re[4]: enum class
От: Videoman Россия https://hts.tv/
Дата: 26.05.18 14:16
Оценка:
Здравствуйте, vopl, Вы писали:

V>смотри ns2::s::Flags, все срабатывает


Да, работает, даже внутри класса. А можно пояснить финт с friend void enableBitMaskOperators(Flags), как это работает?
Отредактировано 26.05.2018 14:16 Videoman . Предыдущая версия .
Re[5]: enum class
От: vopl Россия  
Дата: 26.05.18 14:39
Оценка: 4 (1)
Здравствуйте, Videoman, Вы писали:

V>Да, работает, даже внутри класса. А можно пояснить финт с friend void enableBitMaskOperators(Flags), как это работает?


https://en.cppreference.com/w/cpp/language/friend
наш кейс (1), хотя в принципе можно использовать и (2)

ну а потом происходит:

A name first declared in a friend declaration within class or class template X becomes a member of the innermost enclosing namespace of X, but is not visible for lookup (except argument-dependent lookup that considers X) unless a matching declaration at the namespace scope is provided — see namespaces for details.

то есть, фактически декларируется void ::ns2::enableBitMaskOperators(::ns2::s::Flags), доступный для ADL, которым затем и запитывается SFINAE у бинарного оператора.
Re[6]: enum class
От: Videoman Россия https://hts.tv/
Дата: 26.05.18 20:26
Оценка:
Здравствуйте, vopl, Вы писали:

V>

V>A name first declared in a friend declaration within class or class template X becomes a member of the innermost enclosing namespace of X, but is not visible for lookup (except argument-dependent lookup that considers X) unless a matching declaration at the namespace scope is provided — see namespaces for details.

V>то есть, фактически декларируется void ::ns2::enableBitMaskOperators(::ns2::s::Flags), доступный для ADL, которым затем и запитывается SFINAE у бинарного оператора.

Жесть какая.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.