bit_enum

360 views
Skip to first unread message

Douglas Boffey

unread,
Jun 10, 2014, 6:43:36 AM6/10/14
to std-pr...@isocpp.org
Often, it seems, one needs a number of flags packed into a data type.  An example is found in std::ios with eof_bit, fail_bit and bad_bit.
 
There are a number of existing solutions, each with their own shortcomings:
 
bit fields
The programmer can only refer to a single field at a time.  Sometimes, a few bits may be related and a test needs to be made for any one of them.
There is no underlying type.
There are no bit operations.
There are no subset operations.
 
std::bitset
The bits are unnamed.
The programmer can only refer to a single bit at a time.
The programmer is unable to control the underlying type.
There are no subset operations.
 
std::vector<bool>
The bits are unnamed.
There are no bit operations.
The programmer can only refer to a single bit at a time.
There are no subset operations.
 
enums and enum classes
There are no bit operations.
To use the enumeration as flags, values outside the list of tags are needed.
There are no subset operations.
 
integer types
There is a lack of abstraction.
The bits are unnamed.
There are no subset operations.
 
My proposal is for the introduction of a new type, the bit_enum (or maybe, enum_set?) that behaves like enums except for below:
 
The first tag defaults to 1, not 0.
 
Succeeding tags default to the next power of 2 greater than the preceding tag.
 
Let T be the bit_enum type, UT be the underlying type and @ be any of &, | or ^.  Let the tag values be value[0], value[1], … value[n – 1], where n is the number of tags, and let all = value[0] | value[1] | … value[n – 1].
 
The following functions should be defined:
 
constexpr inline T operator@(T a, T b) {                                                   return static_cast<T>(static_cast<UT>(a) @ static_cast<UT>(b));                                                                                                                                                                                                                                
}

 
 
constexpr inline T operator@=(T &a, T b) {                                                                                                                                                                                                                               
 
return reinterpret_cast<T &>(reinterpret_cast<UT &>(a) @= static_cast<UT>(b));                                                                                                                                                                                                                               
}

 
 
constexpr inline T operator~(T a) {
  return static_cast<T>(static_cast<UT>(a) ^ static_cast<UT>(all));
}

 
Explicit static_casting to/from UT are allowed.
 
 
constexpr inline bool operator==(T a, T b) {          
 
return static_cast<UT>(a) == static_cast<UT>(b);          
}

 
 
constexpr inline bool operator!=(T a, T b) {           
 
return !(a == b);           
}

 
 
constexpr inline bool operator<=(T a, T b) {            
 
return (a & ~b) == static_cast<T>(0);            
}

 
 
const inline bool operator<(T a, T b) {             
 
return a <= b && a != b;             
}

 
 
const inline bool operator>=(T a, T b) {              
 
return b <= a;              
}

 
 
const inline bool operator>(T a, T b) {               
 
return b < a;               
}

 
 
const inline operator bool(T a) {                
 
return static_cast<UT>(a);                
}

 

dgutson .

unread,
Jun 10, 2014, 9:37:09 AM6/10/14
to std-proposals
FWIW, I faced a similar problem several years ago so I made a library
you made want to look at:
https://code.google.com/p/mili/source/browse/mili/bitwise_enums.h

I'm not saying that a language support is not needed, just that
looking at what can be done from a library is worth looking.

Cheers,

Daniel.
> --
>
> ---
> You received this message because you are subscribed to the Google Groups
> "ISO C++ Standard - Future Proposals" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to std-proposal...@isocpp.org.
> To post to this group, send email to std-pr...@isocpp.org.
> Visit this group at
> http://groups.google.com/a/isocpp.org/group/std-proposals/.



--
Who’s got the sweetest disposition?
One guess, that’s who?
Who’d never, ever start an argument?
Who never shows a bit of temperament?
Who's never wrong but always right?
Who'd never dream of starting a fight?
Who get stuck with all the bad luck?

David Krauss

unread,
Jun 10, 2014, 9:45:29 AM6/10/14
to std-pr...@isocpp.org
On 2014–06–10, at 6:43 PM, Douglas Boffey <douglas...@gmail.com> wrote:

enums and enum classes
There are no bit operations.
To use the enumeration as flags, values outside the list of tags are needed.
There are no subset operations.

See my StackOverflow answer to "How does one use an enum class as a set of flags?” for a library-style facility. In bullet summary form,

  • Generic operator overloads are provided for enumerations marked as bitsets.
  • A trait flag marks the enumeration as such.
  • Enum values are bit indexes but bitwise operations produce bitset values.
  • Bitset values are derived from std::bitset.

Other answers there provide lighter-weight, non-template alternatives. But, if you have more than a few enumeration types with special properties (bitsets, easy conversion by unary +, working as indexes), having such trait-based library can really pay off.

Matthew Woehlke

unread,
Jun 10, 2014, 1:43:25 PM6/10/14
to std-pr...@isocpp.org
On 2014-06-10 06:43, Douglas Boffey wrote:
> Often, it seems, one needs a number of flags packed into a data type. An
> example is found in std::ios with eof_bit, fail_bit and bad_bit.
>
> My proposal is for the introduction of a new type, the bit_enum (or maybe,
> enum_set?) that behaves like enums except for below:
>
> The first tag defaults to 1, not 0.
>
> Succeeding tags default to the next power of 2 greater than the preceding
> tag.
>
> Let T be the bit_enum type, UT be the underlying type and @ be any of &, |
> or ^. Let the tag values be value[0], value[1], … value[n – 1], where n is
> the number of tags, and let *all* = value[0] | value[1] | … value[n – 1].

Personally, I'd attack this as two (three) problems.

The first requires a language extension to change the default value
assignment of an enum. (Although I wonder how useful this actually is...
it's convenience, undisputably, but there are potential drawbacks, and
the value add is low).

The second can be a library-only change to create a std::flags class.

A recommended third would be a way to make enum values accessible in
another scope, e.g. 'using enum':

enum class /* bitwise */ MyFlag
{
A,
B,
C,
};

class MyFlags : std::flags<MyFlag>
{
using enum MyFlag; // A, etc. now in scope of MyFlags
static auto AB = A | B; // AB has type std::flags<MyFlag>
};

(Subclassing may be an issue; with template inheritance, partial
specialization may be preferable. However, the above should suffice to
communicate the general idea. In particular, making the flag enum only
individual flags, and placing convenience flag combinations in the flags
type.)

Note that 'using enum' should be usable from any scope, including
global, local, and namespaces.

See
https://groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/G1_ocEwiqsE
(and
https://groups.google.com/a/isocpp.org/group/std-proposals/attach/c061369a016e9e7e/c++1y-std-flags.h?part=0.1&authuser=0)
for previous discussion and my suggestion on the library implementation.

(See also David Krauss's library; I didn't look, but it sounds like he
has a better approach to providing the global operators...)

> constexpr inline bool operator<=(T a, T b) {
> return (a & ~b) == static_cast<T>(0);
> }

I'm not sure that ordering operations make sense. (Only if you need to
use them as keys in e.g. std::map.) I don't see that there is a logical
meaning to an ordering comparison of flags.

--
Matthew

Douglas Boffey

unread,
Jun 11, 2014, 7:48:43 AM6/11/14
to std-pr...@isocpp.org, mw_t...@users.sourceforge.net
>  constexpr inline bool operator<=(T a, T b) {            
>   return (a & ~b) == static_cast<T>(0);            
> }

I'm not sure that ordering operations make sense. (Only if you need to
use them as keys in e.g. std::map.) I don't see that there is a logical
meaning to an ordering comparison of flags.

The way I defined operator<= etc. was to mirror subset operations.
 

Matthew Woehlke

unread,
Jun 11, 2014, 2:12:13 PM6/11/14
to std-pr...@isocpp.org
Ah. Um... that seems very confusing. (And also like it could break badly
if one does in fact try to use flags as a std::map key.)

If these operations are felt to be needed, I would strongly encourage
using named member functions instead, e.g. contains().

--
Matthew

adrien courdavault

unread,
Feb 2, 2015, 5:55:43 AM2/2/15
to std-pr...@isocpp.org
Hello

I agree with the problem list, but I think the solmution here is maybe more interesting for bit masks with typed enums:

https://www.justsoftwaresolutions.co.uk/cplusplus/using-enum-classes-as-bitfields.html

which requires only the include of a header in the enum class header and a definition for SFINAE to specify if the overloads of bitwise operators should be implemented for the type.

Also, I would say that when I use typed enum I often need to declare C arrays, or std::arrays, and access those.
In which case maybe there could be a solution of the same kind.

there is always the std::underlying_type<> anyway

Best

David Krauss

unread,
Feb 3, 2015, 3:31:24 AM2/3/15
to std-pr...@isocpp.org
On 2015–02–02, at 6:55 PM, adrien courdavault <adrien...@gmail.com> wrote:

Hello

I agree with the problem list, but I think the solmution here is maybe more interesting for bit masks with typed enums:


You can take it a “bit” further and use std::bitset as the flag type: http://stackoverflow.com/a/18554839/153285 .

This saves the trouble of manually entering the powers of two, and allows more than 64 bits. (However, implementations seldom provide efficient small-bitset specializations.)

Tony V E

unread,
Feb 3, 2015, 3:00:27 PM2/3/15
to Andrzej Krzemieński

On Mon, Feb 2, 2015 at 5:55 AM, adrien courdavault <adrien...@gmail.com> wrote:
Hello

I agree with the problem list, but I think the solmution here is maybe more interesting for bit masks with typed enums:

https://www.justsoftwaresolutions.co.uk/cplusplus/using-enum-classes-as-bitfields.html

which requires only the include of a header in the enum class header and a definition for SFINAE to specify if the overloads of bitwise operators should be implemented for the type.


It is unfortunate that the opt-in flag can't just be part of the enum:

#include <type_traits>

enum class Breakfast {
    enable_bitwise_ops = 1,  // give me the ops!
    GreenEggs = 0x1,
    Ham = 0x2
};

template<typename E>
typename std::enable_if<E::enable_bitwise_ops,E>::type
operator|(E lhs,E rhs){
    typedef typename std::underlying_type<E>::type underlying;
    return static_cast<E>(
        static_cast<underlying>(lhs) | static_cast<underlying>(rhs));
}


int main() {
    // your code goes here
    Breakfast b = Breakfast::GreenEggs | Breakfast::Ham;
    return 0;
}

Unfortunately doesn't work. :-(
Or maybe I didn't try hard enough?  Is there something I can put in the enable-if to detect that enable_bitwise_ops is part of the enum?
(Of course, you could also argue that it isn't the best solution - since it makes 'enable_bitwise_ops' a Breakfast value.  But it is slightly nicer (to declare) than specializing a template.)

Tony


Tony V E

unread,
Feb 3, 2015, 3:03:50 PM2/3/15
to Andrzej Krzemieński
On Tue, Feb 3, 2015 at 3:00 PM, Tony V E <tvan...@gmail.com> wrote:


On Mon, Feb 2, 2015 at 5:55 AM, adrien courdavault <adrien...@gmail.com> wrote:
Hello

I agree with the problem list, but I think the solmution here is maybe more interesting for bit masks with typed enums:

https://www.justsoftwaresolutions.co.uk/cplusplus/using-enum-classes-as-bitfields.html

which requires only the include of a header in the enum class header and a definition for SFINAE to specify if the overloads of bitwise operators should be implemented for the type.


It is unfortunate that the opt-in flag can't just be part of the enum:

#include <type_traits>

enum class Breakfast {
    enable_bitwise_ops = 1,  // give me the ops!
    GreenEggs = 0x1,
    Ham = 0x2
};


Needed a bool conversion here:
 
template<typename E>
typename std::enable_if<E::enable_bitwise_ops,E>::type

typename std::enable_if<bool(E::enable_bitwise_ops),E>::type
 
operator|(E lhs,E rhs){
    typedef typename std::underlying_type<E>::type underlying;
    return static_cast<E>(
        static_cast<underlying>(lhs) | static_cast<underlying>(rhs));
}


int main() {
    // your code goes here
    Breakfast b = Breakfast::GreenEggs | Breakfast::Ham;
    return 0;
}

Unfortunately doesn't work. :-(
Or maybe I didn't try hard enough?  Is there something I can put in the enable-if to detect that enable_bitwise_ops is part of the enum?
(Of course, you could also argue that it isn't the best solution - since it makes 'enable_bitwise_ops' a Breakfast value.  But it is slightly nicer (to declare) than specializing a template.)


Works now. Still questionable.

 
Tony



David Krauss

unread,
Feb 3, 2015, 7:58:59 PM2/3/15
to std-pr...@isocpp.org

On 2015–02–04, at 4:03 AM, Tony V E <tvan...@gmail.com> wrote:

Works now. Still questionable.

If you don’t like explicit/foreign template specialization, ADL is another way to implement type-trait lookup.

(This still won’t find the operator functions by ADL unless they’re in the same namespace as the enumeration. The traits lookup is associated by ADL, though.)

namespace utility {
template< typename >
struct tag {};
}

namespace mystuff {
enum class Breakfast {

    GreenEggs = 0x1,
    Ham = 0x2
};

constexpr bool has_bitwise_ops( utility::tag< Breakfast > )
    { return true; }
}

namespace bitenum {
constexpr bool has_bitwise_ops( ... ) { return false; }

template< typename E >
std::enable_if_t< has_bitwise_ops( utility::tag< E >{} ),
E > operator | ( E lhs, E rhs ) {
   ...

Myriachan

unread,
Feb 3, 2015, 8:21:39 PM2/3/15
to std-pr...@isocpp.org
On Tuesday, February 3, 2015 at 4:58:59 PM UTC-8, David Krauss wrote:
namespace mystuff {
enum class Breakfast {
    GreenEggs = 0x1,
    Ham = 0x2
};


It's a horrible hack, but it's actually possible with macros to automate the process of determining the next flag in bitfield enums.

#include <cstdio>
#include <cstdint>

#define FLAG_CONCAT2(x, y) x ## y
#define FLAG_CONCAT(x, y) FLAG_CONCAT2(x, y)
#define FLAG(name) \
    FLAG_CONCAT
(DUMMY_FLAG_, __LINE__), \
    name
= static_cast<decltype(FLAG_CONCAT(DUMMY_FLAG_, __LINE__))>( \
        FLAG_CONCAT
(DUMMY_FLAG_, __LINE__) ? \
       
((FLAG_CONCAT(DUMMY_FLAG_, __LINE__) - 1) << 1) : 1)

enum class Meow : std::uint8_t
{
    FLAG
(Test0),
    FLAG
(Test1),
    FLAG
(Test2),
    FLAG
(Test3),
    FLAG
(Test4),
    FLAG
(Test5),
    FLAG
(Test6),
    FLAG
(Test7),
};

int main()
{
    std
::printf("%d %d %d %d %d %d %d %d\n",
       
static_cast<int>(Meow::Test0), static_cast<int>(Meow::Test1),
       
static_cast<int>(Meow::Test2), static_cast<int>(Meow::Test3),
       
static_cast<int>(Meow::Test4), static_cast<int>(Meow::Test5),
       
static_cast<int>(Meow::Test6), static_cast<int>(Meow::Test7));
   
return 0;
}


C:\Projects\temp\tests>enumflags
1 2 4 8 16 32 64 128

David Krauss

unread,
Feb 3, 2015, 8:27:09 PM2/3/15
to std-pr...@isocpp.org

On 2015–02–04, at 9:21 AM, Myriachan <myri...@gmail.com> wrote:

It's a horrible hack, but it's actually possible with macros to automate the process of determining the next flag in bitfield enums.

That’s not the problem at hand. We’re talking about generic operator functions.

I did link earlier to a power-of-two avoidance trick, which involves implicit conversion from a bit-index enumeration to a bitset class. Such a conversion can be built into generic operators.

Gabriel Dos Reis

unread,
Feb 6, 2015, 9:42:04 PM2/6/15
to std-pr...@isocpp.org
Tony V E <tvan...@gmail.com> writes:

[...]

| template<typename E>
| typename std::enable_if<E::enable_bitwise_ops,E>::type
^^^^^^^^^^^^^^^^^^^^^

Just allow it to be well-behaved enums.

| operator|(E lhs,E rhs){
|     typedef typename std::underlying_type<E>::type underlying;
|     return static_cast<E>(
|         static_cast<underlying>(lhs) | static_cast<underlying>(rhs));
| }

Interestingly, I have been using this technique in production for
at least half a decade now. Coupled with this is my general technique of
introducing new, distinction, efficient integer types that enjoye the
same builtin efficiency as existing integer types. E.g.

enum class byte : uint8_t { };

I call this sort of scoped enums (that introduce no new enumerators)
"integer classes". They work very well. See my recent message to the
EWG reflector and ideas about to simplify construction.

-- Gaby

David Krauss

unread,
Feb 6, 2015, 11:33:00 PM2/6/15
to std-pr...@isocpp.org

> On 2015–02–07, at 10:41 AM, Gabriel Dos Reis <g...@axiomatics.org> wrote:
>
> Just allow it to be well-behaved enums.

In a better world, compilers would understand such definitions.

> enum class byte : uint8_t { };
>
> I call this sort of scoped enums (that introduce no new enumerators)
> "integer classes". They work very well.

What do you think of the strong typedefs proposal? Might there be motivation for its implementation?

> See my recent message to the
> EWG reflector and ideas about to simplify construction.

Perhaps the readers of this public list would appreciate the opportunity to do so, but that list is limited-access.

Tony V E

unread,
Feb 7, 2015, 12:42:33 AM2/7/15
to Standard Proposals
On Fri, Feb 6, 2015 at 9:41 PM, Gabriel Dos Reis <g...@axiomatics.org> wrote:
Tony V E <tvan...@gmail.com> writes:

[...]

| template<typename E>
| typename std::enable_if<E::enable_bitwise_ops,E>::type
                          ^^^^^^^^^^^^^^^^^^^^^

Just allow it to be well-behaved enums.

I really don't understand what you mean (your terseness often baffles me).  Would you replace it with is_enum<E> or ...?

Tony

Matthew Woehlke

unread,
Feb 9, 2015, 12:06:26 PM2/9/15
to std-pr...@isocpp.org
On 2015-02-06 23:32, David Krauss wrote:
>> On 2015–02–07, at 10:41 AM, Gabriel Dos Reis wrote:
>> enum class byte : uint8_t { };
>>
>> I call this sort of scoped enums (that introduce no new enumerators)
>> "integer classes". They work very well.
>
> What do you think of the strong typedefs proposal? Might there be motivation for its implementation?

Getting a bit off-topic, but... I'd like to see strong typedefs. I've
rolled my own in at least one application.

My use case is I have a mesh class with various methods that operate on
various indexed items of the mesh, e.g. vertices, edges and faces. I use
strongly-typed integer classes to differentiate between each type of
index in order to reduce the risk of accidentally conflating them. (It
also allows overloading methods based on the index type, although this
is dubious API and I don't think I'm using it much if at all.)

--
Matthew

Anthony Williams

unread,
Feb 9, 2015, 12:22:49 PM2/9/15
to std-pr...@isocpp.org
On 09/02/15 17:06, Matthew Woehlke wrote:
> On 2015-02-06 23:32, David Krauss wrote:
>>> On 2015–02–07, at 10:41 AM, Gabriel Dos Reis wrote:
>>> enum class byte : uint8_t { };
>>>
>>> I call this sort of scoped enums (that introduce no new enumerators)
>>> "integer classes". They work very well.
>>
>> What do you think of the strong typedefs proposal? Might there be motivation for its implementation?
>
> Getting a bit off-topic, but... I'd like to see strong typedefs. I've
> rolled my own in at least one application.

I've rolled my own too in the past. In C++11, I wonder whether we need a
special syntax.

Inherited constructors essentially give us strong typedefs for class
types, and scoped enums do that for integral types.

Do we need strong typedefs for anything else?

Anthony
--
Author of C++ Concurrency in Action http://www.stdthread.co.uk/book/
just::thread C++11 thread library http://www.stdthread.co.uk
Just Software Solutions Ltd http://www.justsoftwaresolutions.co.uk
15 Carrallack Mews, St Just, Cornwall, TR19 7UL, UK. Company No. 5478976

Matthew Woehlke

unread,
Feb 9, 2015, 1:50:09 PM2/9/15
to std-pr...@isocpp.org
On 2015-02-09 12:22, Anthony Williams wrote:
> On 09/02/15 17:06, Matthew Woehlke wrote:
>> On 2015-02-06 23:32, David Krauss wrote:
>>>> On 2015–02–07, at 10:41 AM, Gabriel Dos Reis wrote:
>>>> enum class byte : uint8_t { };
>>>>
>>>> I call this sort of scoped enums (that introduce no new enumerators)
>>>> "integer classes". They work very well.
>>>
>>> What do you think of the strong typedefs proposal? Might there be motivation for its implementation?
>>
>> Getting a bit off-topic, but... I'd like to see strong typedefs. I've
>> rolled my own in at least one application.
>
> I've rolled my own too in the past. In C++11, I wonder whether we need a
> special syntax.
>
> Inherited constructors essentially give us strong typedefs for class
> types, and scoped enums do that for integral types.

Er... how? (Not the classes, the integers...)

If I do this:

enum class Foo : int {};

I can't seem to do anything useful with this. For example, I can't
brace-initialize it, assign it, pass an integer to a function taking a
Foo in any obvious manner, add to it, etc. I can do all of these with my
class. In particular, I would expect a proper strong typedef to
implement all of the usual integer operations (+,-,*,/,%,++,+=,etc.) for
a strongly typed integer.

--
Matthew

Anthony Williams

unread,
Feb 9, 2015, 2:29:54 PM2/9/15
to std-pr...@isocpp.org

You can add the operators as has been discussed for the bitmask operators.

Implicit conversions seem wrong for a strong typedef, IMHO. The lack of such is the chief benefit over a normal typedef.

Initialisation might be an issue, but you could easily do a cast or write a factory function.

Anthony

Matthew Woehlke

unread,
Feb 9, 2015, 2:43:59 PM2/9/15
to std-pr...@isocpp.org
On 2015-02-09 14:29, Anthony Williams wrote:
> On 9 Feb 2015 18:50, "Matthew Woehlke" wrote:
>> On 2015-02-09 12:22, Anthony Williams wrote:
>>> I've rolled my own [strongly typed integers] in the past. In
>>> C++11, I wonder whether we need a special syntax.
>>>
>>> Inherited constructors essentially give us strong typedefs for class
>>> types, and scoped enums do that for integral types.
>>
>> Er... how? (Not the classes, the integers...)
>>
>> If I do this:
>>
>> enum class Foo : int {};
>>
>> I can't seem to do anything useful with this. For example, I can't
>> brace-initialize it, assign it, pass an integer to a function taking a
>> Foo in any obvious manner, add to it, etc. I can do all of these with my
>> class. In particular, I would expect a proper strong typedef to
>> implement all of the usual integer operations (+,-,*,/,%,++,+=,etc.) for
>> a strongly typed integer.
>
> You can add the operators as has been discussed for the bitmask operators.

...but that's exactly my point. A language level strong typedef need not
have this limitation. If I have to define the operators myself there is
no benefit - and in fact, there are instead drawbacks - to using an enum
to "fake it".

> Implicit conversions seem wrong for a strong typedef, IMHO. The lack of
> such is the chief benefit over a normal typedef.

Yes, but the *only way* to construct such a critter from what I can tell
is with a static cast. That's just ugly. Given a "properly implemented"
strong typedef ('Foo'), I expect these to work:

void foo(Foo); foo({0});
auto foo = Foo{0};

I would certainly expect these to work with a language feature. In
addition, I would expect all standard integer operators to Just Work,
without needing to do anything to implement them myself.

IOW, going back to your statement, "I wonder whether we need a special
syntax"... IMHO yes we do. At least for integers. (Oh... and what about
float/double?)

--
Matthew

Douglas Boffey

unread,
Feb 9, 2015, 3:30:21 PM2/9/15
to std-pr...@isocpp.org
Floats and doubles, so physical equations are dimensionally correct.

Douglas Boffey

unread,
Feb 9, 2015, 3:38:15 PM2/9/15
to std-pr...@isocpp.org
So that, e.g. adding a velocity to a time, and assigning to a variable
expecting a mass wouldn't compile.

Hyman Rosen

unread,
Feb 17, 2015, 2:51:23 PM2/17/15
to std-pr...@isocpp.org
Strong typedef isn't the same as unit.  If I were to say (taking a page from Ada)
    typedef new int MyInt;
then I should be able to do
    MyInt x{3};
    x = x * x * x;

but if I had
    typedef SI::Time MySec;
    MySec x{3};

then
    x = x * x * x;
should not compile.

David Krauss

unread,
Feb 17, 2015, 6:00:50 PM2/17/15
to std-pr...@isocpp.org
On 2015–02–18, at 3:51 AM, Hyman Rosen <hyman...@gmail.com> wrote:

Strong typedef isn't the same as unit. 

The stated main purpose of strong typedefs is to implement units.

If I were to say (taking a page from Ada)
    typedef new int MyInt;
then I should be able to do
    MyInt x{3};
    x = x * x * x;

but if I had
    typedef SI::Time MySec;
    MySec x{3};

then
    x = x * x * x;
should not compile.

You have to define the behavior of operators over strong typedefs by declaring them, usually with defaulted definitions.

Gabriel Dos Reis

unread,
Feb 19, 2015, 7:16:17 AM2/19/15
to std-pr...@isocpp.org
David Krauss <pot...@gmail.com> writes:

| > On 2015–02–07, at 10:41 AM, Gabriel Dos Reis <g...@axiomatics.org> wrote:
| >
| > Just allow it to be well-behaved enums.
|
| In a better world, compilers would understand such definitions.
|
| > enum class byte : uint8_t { };
| >
| > I call this sort of scoped enums (that introduce no new enumerators)
| > "integer classes". They work very well.
|
| What do you think of the strong typedefs proposal? Might there be
| motivation for its implementation?

Clearly, it is somehow related, but the devil is in the details.
This is something has been proposed several times. Usually, there are
questions about how to construct values of such type, which conversions
are permitted, which aren't. The usual discussions and confusion come
from the fact that 'strong typedef' or 'opaque typedefs' tend to mean
different things to different people.

|
| > See my recent message to the
| > EWG reflector and ideas about to simplify construction.
|
| Perhaps the readers of this public list would appreciate the
| opportunity to do so, but that list is limited-access.

-- Gaby

Gabriel Dos Reis

unread,
Feb 19, 2015, 7:19:25 AM2/19/15
to std-pr...@isocpp.org
Matthew Woehlke <mw_t...@users.sourceforge.net> writes:

| On 2015-02-09 12:22, Anthony Williams wrote:
| > On 09/02/15 17:06, Matthew Woehlke wrote:
| >> On 2015-02-06 23:32, David Krauss wrote:
| >>>> On 2015–02–07, at 10:41 AM, Gabriel Dos Reis wrote:
| >>>> enum class byte : uint8_t { };
| >>>>
| >>>> I call this sort of scoped enums (that introduce no new enumerators)
| >>>> "integer classes". They work very well.
| >>>
| >>> What do you think of the strong typedefs proposal? Might there be
| >>> motivation for its implementation?
| >>
| >> Getting a bit off-topic, but... I'd like to see strong typedefs. I've
| >> rolled my own in at least one application.
| >
| > I've rolled my own too in the past. In C++11, I wonder whether we need a
| > special syntax.
| >
| > Inherited constructors essentially give us strong typedefs for class
| > types, and scoped enums do that for integral types.
|
| Er... how? (Not the classes, the integers...)
|
| If I do this:
|
| enum class Foo : int {};
|
| I can't seem to do anything useful with this.

Actually, you can.

| For example, I can't
| brace-initialize it, assign it, pass an integer to a function taking a
| Foo in any obvious manner, add to it, etc. I can do all of these with my
| class. In particular, I would expect a proper strong typedef to
| implement all of the usual integer operations (+,-,*,/,%,++,+=,etc.) for
| a strongly typed integer.

This is usually where agreements "strong typedefs" stop. If I do

enum class SizeType : uint32_t { };

It is not clear I want all usuall arithmetic types to be valid for
SizeType.

-- Gaby

Gabriel Dos Reis

unread,
Feb 19, 2015, 7:26:47 AM2/19/15
to std-pr...@isocpp.org
Tony V E <tvan...@gmail.com> writes:

| On Fri, Feb 6, 2015 at 9:41 PM, Gabriel Dos Reis <g...@axiomatics.org>
| wrote:
|
| Tony V E <tvan...@gmail.com> writes:
|
| [...]
|
| | template<typename E>
| | typename std::enable_if<E::enable_bitwise_ops,E>::type
|                           ^^^^^^^^^^^^^^^^^^^^^
|
| Just allow it to be well-behaved enums.
|
|
| I really don't understand what you mean (your terseness often baffles
| me).  Would you replace it with is_enum<E> or ...?

Almost: a test that E is a scoped enum. For example, something like

#include <type_traits>

template<typename T>
using raw = std::underlying_type_t<T>;

template<typename T, bool = std::is_enum<T>::value>
struct wellbehaved_enum_impl : std::false_type { };

template<typename T>
struct wellbehaved_enum_impl<T, true> {
enum { value = not std::is_convertible<T, raw<T>>::value };
};

template<typename T>
struct wellbehaved_enum : wellbehaved_enum_impl<T> { };

template<typename T>
using check_type = std::enable_if_t<wellbehaved_enum<T>::value, T>;

template<typename T>
constexpr check_type<T> operator&(T x, T y) {
return T(raw<T>(x) & raw<T>(y));
}

template<typename T>
constexpr check_type<T> operator|(T x, T y) {
return T(raw<T>(x) | raw<T>(y));
}

template<typename T>
inline check_type<T>& operator&=(T& x, T y) {
return x = x & y;
}

template<typename T>
inline check_type<T>& operator|=(T& x, T y) {
return x = x | y;
}


enum class Foo {
a, b
};

int main() {
auto x = Foo::a | Foo::b;
auto y = Foo::a & Foo::b;
}

-- Gaby

Douglas Boffey

unread,
Feb 19, 2015, 8:46:45 AM2/19/15
to std-pr...@isocpp.org
Perhaps we need something like:

Foo operator&(Foo, Foo) = default;

etc., where Foo is an enum class?

Hyman Rosen

unread,
Feb 19, 2015, 4:40:52 PM2/19/15
to std-pr...@isocpp.org
I would expect strong typedefs for the arithmetic types to allow all the normal operations, but not to interconvert with other arithmetic types.  For units, I've always liked the approach used in Barton & Nackman Scientific and Engineering C++:

namespace SimpleSI {
    template <typename ValueType, int Length, int Mass, int Time> struct Unit {
        ValueType value;
        explicit Unit(ValueType v = ValueType()) : value(v) { }
        Unit operator+(Unit other) const { return Unit(value + other.value); }

        Unit operator-(Unit other) const { return Unit(value - other.value); }
    }

    template <typename ValueType> struct Unit<ValueType, 0, 0, 0> {
        ValueType value;
        Unit(ValueType v = ValueType()) : value(v) { }
        operator 
ValueType() const { return value; }
        Unit operator+(Unit other) const { return Unit(value + other.value); }

        Unit operator-(Unit other) const { return Unit(value - other.value); }
    }

    
template <typename ValueType, int Length1, int Mass1, int Time1,
                                  int Length2, int Mass2, int Time2>
    Unit<ValueType, Length1 + Length2, Mass1 + Mass2, Time1 + Time2>
    operator*(Unit<
ValueType, Length1, Mass1, Time1> a,
              
Unit<ValueType, Length2, Mass2, Time2> b) {
        return 
Unit<ValueType, Length1 + Length2, Mass1 + Mass2, Time1 + Time2>(a.value * b.value);
    }

    
template <typename ValueType, int Length1, int Mass1, int Time1,
                                  int Length2, int Mass2, int Time2>
    Unit<ValueType, Length1 - Length2, Mass1 - Mass2, Time1 - Time2>
    operator/(Unit<
ValueType, Length1, Mass1, Time1> a,
              
Unit<ValueType, Length2, Mass2, Time2> b) {
        return 
Unit<ValueType, Length1 - Length2, Mass1 - Mass2, Time1 - Time2>(a.value / b.value);
    }
}

Gabriel Dos Reis

unread,
Feb 20, 2015, 9:33:19 AM2/20/15
to std-pr...@isocpp.org
Douglas Boffey <douglas...@gmail.com> writes:

| Perhaps we need something like:
|
| Foo operator&(Foo, Foo) = default;
|
| etc., where Foo is an enum class?

Possibly.

-- Gaby

Tony V E

unread,
Feb 20, 2015, 4:09:13 PM2/20/15
to Standard Proposals
OK, thanks for the explanation.  But I don't want all my enums to have bit-wise operations.  Sometimes - usually I think - enum values are meant to be mutually exclusive.   So I only want bitwise operations on the ones that opt-in.  I'd be somewhat OK if in general Foo & Foo returned a raw<Foo> (to be compatible with C-style enums), but only the ones that opt-in should convert back to Foo. (Your uses might be different of course.)

Tony

Gabriel Dos Reis

unread,
Feb 22, 2015, 3:22:27 PM2/22/15
to std-pr...@isocpp.org
Tony V E <tvan...@gmail.com> writes:

[...]

| OK, thanks for the explanation.  But I don't want all my enums to have
| bit-wise operations.  Sometimes - usually I think - enum values are
| meant to be mutually exclusive.   So I only want bitwise operations on
| the ones that opt-in.  I'd be somewhat OK if in general Foo & Foo
| returned a raw<Foo> (to be compatible with C-style enums),

That is what classic non-well behaved enums are for :-)

| but only
| the ones that opt-in should convert back to Foo. (Your uses might be
| different of course.)

std::bitset?

-- Gaby
Reply all
Reply to author
Forward
0 new messages