Is there any interest in type-safe container of bool flags with noexcept guarantees?

Hello Boost community,
I'd like to determine interest in this kind of container.
Motivation
For managing sets of bool flags developers often use std::bitset or c-like
manual bit manipulations. Each approach brings problems.
Some disadvantages of using std::bitset:
* unpredictable and greedy allocation of bit storage;
* setters/getters can throw exceptions;
* it operates in low-level terms like "a bit number";
* we can easily assign one instance of std::bitset to another with the same
number of bits but semantically they may differ.
Proposal
Instead of managing bits via numbers let's manage them via types.
So start by declaring some types
class eats_meat;
class eats_grass;
class has_tail;
Then bind these types to flag identifiers
typedef typed_flags

AMDG On 03/21/2017 10:31 AM, Роман Орлов via Boost wrote:
<snip> class eats_meat; class eats_grass; class has_tail; Then bind these types to flag identifiers typedef typed_flags
animal;
what's wrong with struct animal { bool eats_meat : 1; bool eats_grass : 1; bool has_tail : 1; }; In Christ, Steven Watanabe

On 21 Mar 2017, at 19:23, Steven Watanabe via Boost
wrote: AMDG
On 03/21/2017 10:31 AM, Роман Орлов via Boost wrote:
<snip> class eats_meat; class eats_grass; class has_tail; Then bind these types to flag identifiers typedef typed_flags
animal; what's wrong with struct animal { bool eats_meat : 1; bool eats_grass : 1; bool has_tail : 1; };
To me standard C bitfields fit the bill, and is what I will use as long as I do not need to do bitwise operations on the flags. For just setting and reading them, it works fine and is simple to use. Beyond that, allthough outside this topic I really wish the language had the ability to take better control of the binary representation and layout of C bitfields for binary data portability. — Bjørn

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; };
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
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;
Of course, it can be done in one line with initializer list
animal a1{0, 1, 1};
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

I agree that bitfields are not the way to go. They suffer from a number of
deficiencies - not least that each bit is *not a separate object* so they
are problematic i multithreaded environments. They also of course carry no
type information.
However, I am struggling to see how the proposed class is any more useful
than a std::tuple classes that support a bool conversion operator.
Can you demonstrate a use case where a tuple is inadequate?
On 22 March 2017 at 17:02, Roman Orlov via Boost
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; };
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
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;
Of course, it can be done in one line with initializer list animal a1{0, 1, 1};
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
{1}, flag {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
{1}} | animal{flag {1}}; 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
{1}}; assert( (!a1.all()) ); assert( (!a1.any ()) ); // with some syntax sugar 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
a; // has p3 property typedef typed_flags b; // has p3 property too template
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}}); _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman /listinfo.cgi/boost

On 22.03.2017 20:14, Richard Hodges via Boost wrote:
I agree that bitfields are not the way to go. They suffer from a number of deficiencies - not least that each bit is *not a separate object* so they are problematic i multithreaded environments. They also of course carry no type information.
However, I am struggling to see how the proposed class is any more useful than a std::tuple classes that support a bool conversion operator.
Can you demonstrate a use case where a tuple is inadequate?
std::tuple and proposed class typed_flags are completely different.
Via std::tuple you can emulate typed bit storage like this
class p1; //
class p2; // incomplete types
class p3; //
template<typename T>
struct bit {
unsigned char value;
};
std::tuple

On 03/22/17 20:14, Richard Hodges via Boost wrote:
I agree that bitfields are not the way to go. They suffer from a number of deficiencies - not least that each bit is *not a separate object* so they are problematic i multithreaded environments. They also of course carry no type information.
Thread safety is a red herring since none of the solutions presented in this discussion are thread safe.
However, I am struggling to see how the proposed class is any more useful than a std::tuple classes that support a bool conversion operator.
Can you demonstrate a use case where a tuple is inadequate?
std::tuple does not compress bits, which is what std::bitset and bitfields do. Also, please, don't top-post. See the discussion policy here: http://www.boost.org/community/policy.html

On 23/03/2017 11:44, Andrey Semashev via Boost wrote:
On 03/22/17 20:14, Richard Hodges via Boost wrote:
I agree that bitfields are not the way to go. They suffer from a number of deficiencies - not least that each bit is *not a separate object* so they are problematic i multithreaded environments. They also of course carry no type information.
Thread safety is a red herring since none of the solutions presented in this discussion are thread safe.
FWIW, I have some code kicking around somewhere that lets you treat a manually bit-mask-valued enum as atomic. But yes, that's off-topic.

On 03/22/17 19:02, Roman Orlov via Boost wrote:
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; };
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
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;
Of course, it can be done in one line with initializer list animal a1{0, 1, 1};
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};
Frankly, all the above problems are solved with constructors. The only real problem I see with the struct approach is that it's lacking expressiveness. From this declaration: animal a1{0, 1, 1}; it is difficult to immediately say what the value of has_tail will be. It would be better if we had named field initialization, like in C, but alas. Usually, I solve this problem with std::bitset with an enum providing names for the flags. enum animal_traits { eats_meat, eats_grass, has_tail, _count }; typedef std::bitset< animal_traits::_count > animal; animal a1; // all traits are false a1[eats_grass] = 1; a1[has_tail] = 1; I think, this provides the necessary expressiveness and performance. I guess, your proposal provides better type safety than this solution, but I'm not sure the improvement is significant enough to outweigh the more complicated syntax. For example, one could write a slightly more complicated version of the above: enum animal_traits { eats_meat, eats_grass, has_tail, _count }; template< typename Enum, unsigned int Count > class typed_bitset : public std::bitset< Count > { public: bool operator[] (Enum idx) const { return std::bitset< Count >::operator[](idx); } }; typedef typed_bitset< animal_traits, animal_traits::_count > animal; animal a1; // all traits are false a1[eats_grass] = 1; a1[has_tail] = 1; a1[10] = 1; // error I suspect it would also be faster to compile.

Le 22/03/2017 à 23:40, Andrey Semashev via Boost a écrit :
On 03/22/17 19:02, Roman Orlov via Boost wrote:
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; };
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
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;
Of course, it can be done in one line with initializer list animal a1{0, 1, 1};
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};
Frankly, all the above problems are solved with constructors.
The only real problem I see with the struct approach is that it's lacking expressiveness. From this declaration:
animal a1{0, 1, 1};
it is difficult to immediately say what the value of has_tail will be. It would be better if we had named field initialization, like in C, but alas.
Usually, I solve this problem with std::bitset with an enum providing names for the flags.
enum animal_traits { eats_meat, eats_grass, has_tail, _count }; typedef std::bitset< animal_traits::_count > animal;
animal a1; // all traits are false a1[eats_grass] = 1; a1[has_tail] = 1;
I think, this provides the necessary expressiveness and performance. I guess, your proposal provides better type safety than this solution, but I'm not sure the improvement is significant enough to outweigh the more complicated syntax. For example, one could write a slightly more complicated version of the above:
enum animal_traits { eats_meat, eats_grass, has_tail, _count };
template< typename Enum, unsigned int Count > class typed_bitset : public std::bitset< Count > { public: bool operator[] (Enum idx) const { return std::bitset< Count >::operator[](idx); } };
typedef typed_bitset< animal_traits, animal_traits::_count > animal;
animal a1; // all traits are false a1[eats_grass] = 1; a1[has_tail] = 1; a1[10] = 1; // error
I suspect it would also be faster to compile. compile time for ordinal sets would suffer a little bit as we need to associate at compile time each enumerator to its position (this could be
I have an ordinal_set<Ordinal> that is type safe.
An enum as the previous one canbe see as an Ordinal and so
enum animal_traits
{
eats_meat, eats_grass, has_tail, _count
};
using animal_traits_set = ordinal_set

On 03/23/17 01:54, Vicente J. Botet Escriba wrote:
I have an ordinal_set<Ordinal> that is type safe. An enum as the previous one canbe see as an Ordinal and so
enum animal_traits { eats_meat, eats_grass, has_tail, _count }; using animal_traits_set = ordinal_set
Is the ordinal_set part of Boost? Where can I see it? Also, how do you deduce the number of bits from the enum type?
typedef typed_bitset< animal_traits, animal_traits::_count > animal;
animal a1; // all traits are false a1[eats_grass] = 1; a1[has_tail] = 1; a1[10] = 1; // error
I suspect it would also be faster to compile. compile time for ordinal sets would suffer a little bit as we need to associate at compile time each enumerator to its position (this could be provided by reflection or by user defined ordinal traits).
Ah, so one would also have to specialize traits for the enum?

Le 23/03/2017 à 00:01, Andrey Semashev via Boost a écrit :
On 03/23/17 01:54, Vicente J. Botet Escriba wrote:
I have an ordinal_set<Ordinal> that is type safe. An enum as the previous one canbe see as an Ordinal and so
enum animal_traits { eats_meat, eats_grass, has_tail, _count }; using animal_traits_set = ordinal_set
Is the ordinal_set part of Boost? Where can I see it? It was enum_set in [TBost.Enums] https://github.com/viboes/enums https://htmlpreview.github.io/?https://github.com/viboes/enums/blob/master/l...
and then it became ordinal_set in [JASEL] https://github.com/viboes/std-make/tree/master/include/experimental/fundamen... The last needs some work yet to adapt enums.
Also, how do you deduce the number of bits from the enum type?
it is the size of the ordinal type. For enums, the number of unique enumerators.
typedef typed_bitset< animal_traits, animal_traits::_count > animal;
animal a1; // all traits are false a1[eats_grass] = 1; a1[has_tail] = 1; a1[10] = 1; // error
I suspect it would also be faster to compile. compile time for ordinal sets would suffer a little bit as we need to associate at compile time each enumerator to its position (this could be provided by reflection or by user defined ordinal traits).
Ah, so one would also have to specialize traits for the enum?
Yes. There could be a linear default trait 0..N trait. Vicente

On 23.03.2017 01:40, Andrey Semashev via Boost wrote:
Usually, I solve this problem with std::bitset with an enum providing names for the flags.
enum animal_traits { eats_meat, eats_grass, has_tail, _count }; typedef std::bitset< animal_traits::_count > animal;
animal a1; // all traits are false a1[eats_grass] = 1; a1[has_tail] = 1;
I think, this provides the necessary expressiveness and performance. I guess, your proposal provides better type safety than this solution, but I'm not sure the improvement is significant enough to outweigh the more complicated syntax. For example, one could write a slightly more complicated version of the above:
enum animal_traits { eats_meat, eats_grass, has_tail, _count };
template< typename Enum, unsigned int Count > class typed_bitset : public std::bitset< Count > { public: bool operator[] (Enum idx) const { return std::bitset< Count >::operator[](idx); } };
typedef typed_bitset< animal_traits, animal_traits::_count > animal;
animal a1; // all traits are false a1[eats_grass] = 1; a1[has_tail] = 1; a1[10] = 1; // error
I suspect it would also be faster to compile.
Using std::bitset in this way is not a good solution. C++ standard doesn't claim that bitset's backing storage should be as compact as possible. In stdlibc++ bits are stored in array of unsigned long items. So on my Ubuntu x64 sizeof(typed_bitset< animal_traits, animal_traits::_count >) == 8 That's not acceptable for only 3 bits. Another problem is that member functions for bit manipulations are not marked as *noexcept*. Using weak enum-based parameterization you can be sure in proper use bits. But the compiler doesn't know it. That's why I've come to making my own container. I need compact storage and noexcept guarantee at compile time.

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
{1}, flag {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
{1}} | animal{flag {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
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
{1}}; assert( (!a1.all()) ); assert( (!a1.any ()) ); // with some syntax sugar If you follow bit_set interface it should be
assert( (!a1.any()) );
The last could be
assert( (!a1.any_of
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
a; // has p3 property typedef typed_flags b; // has p3 property too template
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
participants (8)
-
Andrey Semashev
-
Bjørn Roald
-
Gavin Lambert
-
Richard Hodges
-
Roman Orlov
-
Steven Watanabe
-
Vicente J. Botet Escriba
-
Роман Орлов