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;
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.