В С++ есть замечательная возможность объявить класс перечисления. При этом, мы защищаемся от случайного перехода от целых к перечислениям и обратно. Тут все понятно.
Теперь, в некоторых перечислениях, хотелось бы иметь логические операции |,|=,&,&=,~ и т.д. для работы с флагами. Это тоже без проблем можно сделать, перегрузить операторы и т.д.
Теперь, хотелось бы избавиться от рутины и добавить для всего этого шаблоны, но по некому условию, только если надо, иначе, шаблон для всех перечислений очень "жадная" штука и применяется вообще везде, где надо и не надою.
Есть подход в лоб для этого и он много где описан, в том числе на 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. Необходимо не использовать макросы.
В общем, кто как решает данную проблему и что можете посоветовать?
Здравствуйте, 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");
}
};
|
| |
Здравствуйте, kov_serg, Вы писали:
_>Для совсем больных можно так
Мне не нужно для больных. Я выше описал что мне нужно.
Здравствуйте, 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;
}
Здравствуйте, Videoman, Вы писали:
V>Внутри класса и с приватной секцией этот подход не срабатывает.
смотри ns2::s::Flags, все срабатывает
Здравствуйте, 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 у бинарного оператора.
Здравствуйте, 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 у бинарного оператора.
Жесть какая.