none
The snippet below should not compile according to [dcl.enum]/7 in the C++ Standard. RRS feed

  • Question

  • The following code

    #include <iostream>
    #include <typeinfo>
    
    enum E {
    	a,
    	b = 0x7FFFFFFFFFFFFFFF
    };
    
    int main()
    {
    	std::cout << "sizeof(0x7FFFFFFFFFFFFFFF) " << sizeof(0x7FFFFFFFFFFFFFFF) << '\n';
    	std::cout << "sizeof(enum E) " << sizeof(enum E) << '\n';
    	std::cout << "sizeof(E underlying type) " << sizeof(std::underlying_type<E>::type) << '\n';
    }

    prints

    sizeof(0x7FFFFFFFFFFFFFFF) 8
    sizeof(enum E) 4
    sizeof(E underlying type) 4 

    But the code should not compile according to [dcl.enum]/7 which says (emphases are mine):

    For an enumeration whose underlying type is not fixed, the underlying type is an integral type that can represent all the enumerator values defined in the enumeration.
     
    If no integral type can represent all the enumerator values, the enumeration is ill-formed.
     
    It is implementation-defined which integral type is used as the underlying type except that the underlying type shall not be larger than int unless the value of an enumerator cannot fit in an int or unsigned int.
     
    If the enumerator-list is empty, the underlying type is as if the enumeration had a single enumerator with value 0.

    PS: The compiler just emits a warning (C4309) for the initialization.

    b = 0x7FFFFFFFFFFFFFFF


    • Edited by Belloc Friday, August 16, 2019 8:52 PM typo
    Friday, August 16, 2019 6:20 PM

Answers

  • This is a bug in Visual C++, just not the one that you think.

    There is an integral type that can store the value given for Visual C++. If you hover over the 0x7FFFFFFFFFFFFFFF in the cout member function call rather than hover over it in the value assignment for the enum you will see something like:

    So Visual C++ detects the type of this constant as long long. According to the rule that you quoted, the compiler should be choosing either long long or unsigned long long as the underlying type for the enum. This is because it cannot fit the value in an int or an unsigned int and long long is the smallest type that it can choose as the underlying type for the selected value to fit.

    If you explicitly use long long or unsigned long long then the compiler will use this and not truncate making the size of the enum 8 bytes.

    sizeof(0x7FFFFFFFFFFFFFFF) 8
    sizeof(enum E) 8
    sizeof(E underlying type) 8

    But without this the compiler will choose int and truncate the initialisation of b to 0xFFFFFFFF. This is the warning that you are getting. The /we compiler option that you mentioned is a documented command line option that tells the compiler to treat the warning that you supply as an error.

    Using a modified version of your code:

    #include <iostream>
    #include <typeinfo>
    
    enum E {
    	a,
    	b = 0x7FFFFFFFFFFFFFFFU
    };
    
    int main()
    {
    	std::cout << "sizeof(0x7FFFFFFFFFFFFFFF) " << sizeof(0x7FFFFFFFFFFFFFFF) << '\n';
    	std::cout << "sizeof(enum E) " << sizeof(enum E) << '\n';
    	std::cout << "sizeof(E underlying type) " << sizeof(std::underlying_type<E>::type) << '\n';
    	if (typeid(std::underlying_type_t<E>) == typeid(long))
    	{
    		std::cout << "Underlying type is long\n";
    	}
    	if (typeid(std::underlying_type_t<E>) == typeid(unsigned long))
    	{
    		std::cout << "Underlying type is unsigned long\n";
    	}
    	if (typeid(std::underlying_type_t<E>) == typeid(long long))
    	{
    		std::cout << "Underlying type is long long\n";
    	}
    	if (typeid(std::underlying_type_t<E>) == typeid(unsigned long long))
    	{
    		std::cout << "Underlying type is unsigned long long\n";
    	}
    	return 0;
    }

    basically just to detect the type that it is using, I compiled this code and ran it on a Linux VM. The output that I got was:

    darran@moomoo:~$ clang++-8 -std=c++17 -O3 -o test main.cpp
    darran@moomoo:~$ ./test
    sizeof(0x7FFFFFFFFFFFFFFF) 8
    sizeof(enum E) 8
    sizeof(E underlying type) 8
    Underlying type is unsigned long

    So CLANG 8 is automatically setting the underlying type to unsigned long (Linux is LP64 so it uses long as the 64 bit integer type, Windows is LLP64 so it uses long long as the 64 bit integer type).

    So the bug here is that Visual C++ is incorrectly choosing int as the underlying type when it can't hold the value of the integer that you gave it.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.


    Friday, August 16, 2019 10:39 PM

All replies

  • Apparently that same code compiles using LLVM 8.0 and GCC 9.2.

    But sending a bug report to Microsoft via the Visual Studio Send Feedback option is the best way to go for this.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    • Edited by Darran Rowe Friday, August 16, 2019 7:12 PM
    Friday, August 16, 2019 6:52 PM
  • Apparently that same code compiles using LLVM 8.0 and GCC 9.2.

    They compile but there is no need for a warning as the

    sizeof(underlying bytes) == sizeof(0x7FFFFFFFFFFFFFFF)

    That is, the underlying size selected by the compilers is compatible with the initialization

    b = 0x7FFFFFFFFFFFFFFF;
    I have also found out that if I compile the code in VS2017 with the flag /we4309, the warning turns into an error. Could that be a MS extension?


    Friday, August 16, 2019 8:50 PM
  • This is a bug in Visual C++, just not the one that you think.

    There is an integral type that can store the value given for Visual C++. If you hover over the 0x7FFFFFFFFFFFFFFF in the cout member function call rather than hover over it in the value assignment for the enum you will see something like:

    So Visual C++ detects the type of this constant as long long. According to the rule that you quoted, the compiler should be choosing either long long or unsigned long long as the underlying type for the enum. This is because it cannot fit the value in an int or an unsigned int and long long is the smallest type that it can choose as the underlying type for the selected value to fit.

    If you explicitly use long long or unsigned long long then the compiler will use this and not truncate making the size of the enum 8 bytes.

    sizeof(0x7FFFFFFFFFFFFFFF) 8
    sizeof(enum E) 8
    sizeof(E underlying type) 8

    But without this the compiler will choose int and truncate the initialisation of b to 0xFFFFFFFF. This is the warning that you are getting. The /we compiler option that you mentioned is a documented command line option that tells the compiler to treat the warning that you supply as an error.

    Using a modified version of your code:

    #include <iostream>
    #include <typeinfo>
    
    enum E {
    	a,
    	b = 0x7FFFFFFFFFFFFFFFU
    };
    
    int main()
    {
    	std::cout << "sizeof(0x7FFFFFFFFFFFFFFF) " << sizeof(0x7FFFFFFFFFFFFFFF) << '\n';
    	std::cout << "sizeof(enum E) " << sizeof(enum E) << '\n';
    	std::cout << "sizeof(E underlying type) " << sizeof(std::underlying_type<E>::type) << '\n';
    	if (typeid(std::underlying_type_t<E>) == typeid(long))
    	{
    		std::cout << "Underlying type is long\n";
    	}
    	if (typeid(std::underlying_type_t<E>) == typeid(unsigned long))
    	{
    		std::cout << "Underlying type is unsigned long\n";
    	}
    	if (typeid(std::underlying_type_t<E>) == typeid(long long))
    	{
    		std::cout << "Underlying type is long long\n";
    	}
    	if (typeid(std::underlying_type_t<E>) == typeid(unsigned long long))
    	{
    		std::cout << "Underlying type is unsigned long long\n";
    	}
    	return 0;
    }

    basically just to detect the type that it is using, I compiled this code and ran it on a Linux VM. The output that I got was:

    darran@moomoo:~$ clang++-8 -std=c++17 -O3 -o test main.cpp
    darran@moomoo:~$ ./test
    sizeof(0x7FFFFFFFFFFFFFFF) 8
    sizeof(enum E) 8
    sizeof(E underlying type) 8
    Underlying type is unsigned long

    So CLANG 8 is automatically setting the underlying type to unsigned long (Linux is LP64 so it uses long as the 64 bit integer type, Windows is LLP64 so it uses long long as the 64 bit integer type).

    So the bug here is that Visual C++ is incorrectly choosing int as the underlying type when it can't hold the value of the integer that you gave it.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.


    Friday, August 16, 2019 10:39 PM
  • Thanks for your reply. I have already posted a bug report here.
    Saturday, August 17, 2019 2:09 PM