Boost logo

Boost :

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


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk