2

In the code below, a variable of enumeration type is saved into a bit-field. As I understand it, variable can be retrieved without data loss. However, that doesn't work.

Is the output below in accordance with the standard?

#include<iostream>
using namespace std;
const int M=3, UL=(1<<M)-1;    // range 0 .. 7, fits into 3 bits
enum class ec { e_min=0, e_max=UL};
// in [decl.enum] (8): b_min==0 && b_max==7==2^M-1
struct bitFieldType {
    ec data : M;  // 3 bits
};
int main(){
  bitFieldType bf;
  for (int c=0; c<=UL; ++c){;
    ec enumIn { static_cast<ec>(c) }; // initialize enumeration type 
    bf.data = enumIn;                 // copy into bit-field
    ec enumOut{bf.data};              // retrieve enumeration type from bit-field
    cout<<static_cast<int>(enumIn) <<" "<< static_cast<int>(enumOut)
    << " " << (bf.data==enumIn) <<"\n";
  }
}

[dcl.enum] (8): "The size of the smallest bit-field large enough to hold all the values of the enumeration type is max(M, 1) if b min is zero ... ." [class.bit] (4) " the value of an enumerator is stored into a bit-field of the same enumeration type and the number of bits in the bit-field is large enough to hold all the values of that enumeration type (10.2), the original enumerator value and the value of the bit-field shall compare equal."

If so, why does output look like this?

clang++ -Wall -fsanitize=undefined -pedantic -std=c++17 bitf.cpp && ./a.out
0 0 1
1 1 1
2 2 1
3 3 1
4 -4 0
5 -3 0
6 -2 0
7 -1 0
clang++ --version
clang version 9.0.0 (trunk 351995)
Target: x86_64-unknown-linux-gnu

EDIT: added a static_cast<>() such that the code compiles with 'enum class' replaced by plain 'enum'.

With plain 'enum' instead of 'enum class', the output is as expected. Also, with one extra bit in the bit-field, the output is as expected.

Heiko Bloch
  • 388
  • 2
  • 15

2 Answers2

1

Your ec, being scoped, has a fixed underlying type of int, so its values are all those of int and your bit-field is not wide enough to guarantee anything. In practice, it’s probably being interpreted as a signed bit-field which needs to be of width 4 to store the values 4–7 (even though the rule about signedness doesn’t explicitly define the signedness of an enumeration type to be that of its underlying type).

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
  • Is the rule about signedness part of ISO C++17? I don't see it in n4659.pdf. – Heiko Bloch Apr 22 '19 at 08:48
  • @HeikoBloch: No, it was added in P1236R1 just in November. The specification still isn’t perfect, but now it says a lot more of what “everyone” already knows. – Davis Herring Apr 22 '19 at 13:36
0

I read that too fast. You've got bigger problems to begin with the enum class to begin with. Oddly enough if I remove the "class" from the enum declaration that warning goes away;

Is it safe to use an enum in a bit field?

g++     foo.cpp   -o foo
foo.cpp:7:15: warning: ‘bitFieldType::data’ is too small to hold all values of ‘enum class ec’
     ec data : M;  // 3 bits
               ^
foo.cpp: In function ‘int main()’:
foo.cpp:12:19: error: cannot convert ‘int’ to ‘ec’ in initialization
     ec enumIn { c };           // initialize enumeration type
                   ^
<builtin>: recipe for target 'foo' failed
make: *** [foo] Error 1

g++ --version
g++ (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Turns out the default type for enum class is int, which explains the sign extension. Just specify the type and it's fine, still compiles with a warning though.

#include<iostream>
using namespace std;
const int M=3, UL=(1<<M)-1;    // range 0 .. 7, fits into 3 bits
enum class ec : unsigned int { e_min=0, e_max=UL};
// in [decl.enum] (8): b_min==0 && b_max==7==2^M-1
struct bitFieldType {
    ec data : M;  // 3 bits
};
int main(){
  bitFieldType bf;
  for (int c=0; c<=UL; ++c){;
    ec enumIn { (ec)(c) };           // initialize enumeration type
    bf.data = enumIn;          // copy into bit-field
    ec enumOut{bf.data};       // retrieve enumeration type from bit-field
    cout<<static_cast<int>(enumIn) <<" "<< static_cast<int>(enumOut)
    << " " << (bf.data==enumIn) <<"\n";
  }
}
/*
0 0 1
1 1 1
2 2 1
3 3 1
4 4 1
5 5 1
6 6 1
7 7 1
*/
ppetraki
  • 428
  • 4
  • 11