Subject: Re: [boost] Is there any interest in type-safe container of bool flags with noexcept guarantees?
From: Vicente J. Botet Escriba (vicente.botet_at_[hidden])
Date: 2017-03-22 22:45:51


Le 22/03/2017 à 17:02, Roman Orlov via Boost a écrit :

Hi,

I like the idea.
> On 21.03.2017 21:23, Steven Watanabe via Boost wrote:
>>
>> what's wrong with
>> struct animal {
>> bool eats_meat : 1;
>> bool eats_grass : 1;
>> bool has_tail : 1;
>> };
>>
>
Steven is right, your library is kind of a library reflecting the
previous structure.
As we don't have yet reflection, we need tag types to identify the fields.
I would expect that once we have reflection, we could provide a bit mask
interface for the previous structure. But we don't have it yet.

> There are several reasons I don't like that approach
>
> While declaring a variable of type 'animal' you should always keep in
> mind to initialize it with empty initializer
> animal a1; // bit fields are not initialized
> animal a2{}; // now it's ok, bits are set to zeros
>
I believe that this is good, as you could use it as member of POD
structures.
While I agree that it is good to initialize everything as soon as
possible, some times been able to don't initialize is the correct
answer. This is C++.
> When you want to initialize some fields (eats_grass, has_tail) you have
> to write several lines of code
> animal a1{};
> a1.eats_grass = 1;
> a1.has_tail = 1;
>
Well I would replace 1 by true ;-)
I find this interface clear.
> Of course, it can be done in one line with initializer list
> animal a1{0, 1, 1};
>
I agree that positional interfaces don't scale well as you show below.
> But we don't write a code ones, we are about to support it for a long
> time. One day we decided to add a new bit field to structure
> struct animal {
> bool can_fly : 1; // yeah, somebody puts it on the first position
> bool eats_meat : 1;
> bool eats_grass : 1;
> bool has_tail : 1;
> };
>
> What will happen with all of list initializers? There will be bugs we
> can't detect at compile time.
> animal a1{0, 1, 1}; // now it doesn't work properly
>
> We have to find all of such initializers and rewrite them
> animal a1{0, 0, 1, 1};
>
> To prevent this I propose to abstract from strict ordering and use
> typed entities
> animal a1{flag<eats_grass>{1}, flag<has_tail>{1}};
>
> And what about bitwise operations on plain structures? While working
> with bool properties conjunction and disjunction are needed fairly
> often. For each structure you'll have to implement them manually.
> Of course it's better to have it out of the box
> auto a1 = animal{flag<eats_gass>{1}} | animal{flag<eats_meat>{1}};
>
Have you considered to use tag classes that provide this kind of flag
arithmetic?
We write the fftag once, but we use them much more times.

   auto a1 = animal{eats_gass{1}} | animal{eats_meat{1}};

The definition of a tag is not complex

struct eats_gass : flag<eats_gass> {
     using flag<eats_gass>::flag<eats_gass>;
};

BTW, why do we need an argument to the flag class
Why
   auto a1 = animal{flag<eats_gass>{1}} | animal{flag<eats_meat>{1}};

and not simply

   auto a1 = animal{flag<eats_gass>{}} | animal{flag<eats_meat>{}};
> Sometimes it's needed to test that all flags are set or at least one.
> And again for each structure you'll have to implement it manually.
> It's better to have these functions in container
> auto a1 = animal{flag<eats_gass>{1}};
> assert( (!a1.all()) );
> assert( (!a1.any<eats_meat, has_tail>()) ); // with some syntax sugar
If you follow bit_set interface it should be

   assert( (!a1.any()) );

The last could be

   assert( (!a1.any_of<eats_meat, has_tail>()) );

You could have also compile time flags that don't have storage. The
previous any could be as well

   assert( (! (a1 & literal_typed_flag<eats_meat, has_tail>{}) ));

BTW, the construction could be done using these literals

   auto a1 = literal_typed_flag<eats_gass, eats_meat>;

literal_typed_flag could be convertible to any typed_flag that contains
at least those tags.

While I'm for the member functions in your case, if we had compile time
reflection we should use non-member function (at least until we have the
possibility to create new types)

>
> Managing typed entities allows you to test common properties of
> semantically different entities. For example:
> class p1;
> class p2;
> class p3;
> class p4;
>
> typedef typed_flags<p1, p2, p3> a; // has p3 property
> typedef typed_flags<p3, p4> b; // has p3 property too
>
> template<typename T, typename... Args>
> bool test_p(Args&&... args) {
> return (... && args.template test<T>());
> }
> // test p3 property from a and b
> test_p<p3>(a{flag<p3>{1}}, b{flag<p3>{1}});
Here we see that having non-member functions could help avoiding the
not-friendly x.template test<T>

   template<typename T, typename... Args>
   bool test_p(Args&&... args) {
     return (... && test<T>(args));
   }

How typed_flags convert? Can typed_flags<a,b> be convertible to
typed_flags<a,b,c> or typed_flags<a,b,c>?

It could also be interesting to look at the tags proposed in the Range
TS to see if an adaptation could simplify the user interface.

HTH,
Vicente