$include_dir="/home/hyper-archives/boost/include"; include("$include_dir/msg-header.inc") ?>
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