Home > OS >  How to set enum val of fixed size to its max possible value?
How to set enum val of fixed size to its max possible value?

Time:04-29

This is probably simple but I'm not getting it right. I have a "bitmask" enum which has a value all that indicates that all bits are set. However, I can't get it to flip all bits using ~0. The following error appears:

<source>:11:16: error: enumerator value '-1' is outside the range of underlying type 'uint_fast8_t' {aka 'unsigned char'}
   11 |         all = ~0x0,
      |             

Which is strange because it should actually fit into uint8_t no? Here is my code (godbolt):

#include <iostream>

int main()
{
    enum mask_t : uint_fast8_t {
        first = 0x1,
        second = 0x2,
        all = ~0x0,
    } mask;

    mask = all;
}

CodePudding user response:

You can get the maximum value with the help of some meta-programming tricks

#include <limits>
#include <type_traits>
#include <cstdint>

enum mask_t : uint_fast8_t {
  first = 0x1,
  second = 0x2,
  all = std::numeric_limits<std::underlying_type_t<mask_t>>::max()
} mask;

Demo

CodePudding user response:

You need to convert the expression ~0x0 having the type int and a negative value to the type uint_fast8_t like for example

all = static_cast<uint_fast8_t>( ~0x0 ),

Why does the expression have the type int? Because the integer literal 0x0 has the type int and the operator ~ does not change the type of the expression.

From the C Standard

2 The type of an integer literal is the first of the corresponding list in Table 5 in which its value can be represented.

And an object of the type int can represent the literal 0x0.

Here is a demonstration program.

#include <iostream>
#include <iomanip>
#include <type_traits>

int main()
{
        
    std::cout << std::boolalpha << std::is_same_v<int, decltype( 0x0 )> << '\n';
}

The program output is

true

Another way is just to write

all = 0xff,

CodePudding user response:

By default, 0x0 is of type int. So if you try to flip all the bits you'll get -1 which can't be assigned to the type your enumeration was defined to.

Even if you use a suffix for that literal value, like u for example. To indicate that the literal value is of unsigned type. As in ~0x0u. You'd get the maximum of the unsigned int type. Which exceeds the range of the 8-bit integer type you're using. So this doesn't work either.

So you need to tell the language that you want the literal value to be of the type you need first. That can be achieved with a static_cast as demonstrated in other answers:

static_cast<uint_fast8_t>( ~0x0 )

But using hardcoded types and values can get in the way sometimes if you decide to change the type of the enum later. So if you have c 14 available. You can use the std::underlying_type type-trait and make a generic utility like:

template < class T > constexpr std::underlying_type_t< T > enum_max_v = ~static_cast< std::underlying_type_t< T > >(0);
// If you have c  17 available with the inline keyword
// template < class T > inline constexpr std::underlying_type_t< T > enum_max_v = ~static_cast< std::underlying_type_t< T > >(0);

And then used like:

enum mask_t : uint_fast8_t {
    first = 0x1,
    second = 0x2,
    all = enum_max_v< mask_t >,
} mask;

Now you don't have to care about the underlying type of the enumeration.

You can even use std::numeric_limits if you want the right values instead of relying on flipping bits:

template < class T > constexpr std::underlying_type_t< T > enum_max_v = std::numeric_limits< std::underlying_type_t< T > >::max();

Sky is the limit.

  • Related