I found out some interesting thing when I programming:
enum class Foo {
FOO_THING,
FOO_TOO
};
int main() {
Foo foo{1}; // It is OK
Foo foo2(1); // It is an invalid
}
Could you tell me, why foo{1} is OK for compiler, and why foo2(1) is invalid ?
Compiler GCC (g++ (Ubuntu 7.3.0-21ubuntu1~16.04) 7.3.0) says:
$ g++ -Wall -std=c++17 foo.cpp
error: cannot convert ‘int’ to ‘Foo’ in initialization
Foo foo2(1);
I really want to know underlying mechanics. :)))
Edit: Maybe it is some compiler bug...
C++17-specific documentation has the following for braced initializer
Otherwise, if T is a enumeration type that is either scoped or
unscoped with fixed underlying type, and if the braced-init-list has
only one initializer, and if the conversion from the initializer to
the underlying type is non-narrowing, and if the initialization is
direct-list-initialization, then the enumeration is initialized with
the result of converting the initializer to its underlying type.
So foo seems to be conforming valid C++17, but foo2 being not braced initialized is not valid.
To understand the reasons why the two syntax are not both legit you must consider that scoped enums were introduced with standard c++11 to enforce static type checking and have scoped identifiers (i.e. no name pollution anymore).
Foo foo(1) is not working because implicit conversion from integer type to scoped enum is forbidden, otherwise you lose the benefit of scoped enums, and to avoid conflicts during overload resolution.
When using Foo foo{1} you are using list initialization that was introduced with c++11 too, but got an upgrade with c++17, that consist in implicit conversion from int value to enum as reported here, if a set of requirements are satisfied:
Both scoped enumeration types and unscoped enumeration types whose
underlying type is fixed can be initialized from an integer without a
cast, using list initialization, if all of the following is true:
the initialization is direct-list-initialization
the initializer list has only a single element
the enumeration is either scoped or unscoped with underlying type fixed
the conversion is non-narrowing.
This makes it possible to introduce new integer types (e.g. SafeInt) that enjoy the same existing calling conventions as their underlying integer
types, even on ABIs that penalize passing/returning structures by
value.
This syntax is safe and will not interfere with legacy code (written before c++11) because both scoped enums and list-initialization did not exist at the time. Furthermore, as reported in the quote, this enables the use of new integer types (like those of the SafeInt library) without the need to force a static cast for enum types in code that conforms to modern c++ syntax.
Related
I'm converting unscoped enumerations to scoped enumerations, and have run across a puzzle.
Stroustrup, C++ Programming Language, 4th edition, Section 8.4.1, documents that scoped enum classes are not implicitly converted to integral types, and provides code for operators | and & as an example of how to use static_cast to work around that.
Shouldn't the following initialization using the | operator on previously defined enum values be illegal?
enum class FileCopy {
PreviousHDUs = 1,
CurrentHDU = 2,
FollowingHDUs = 4,
AllHDUs = PreviousHDUs | CurrentHDU | FollowingHDUs,
CurrentHeader = 8
};
int main()
{
std::cout << static_cast<int>( FileCopy::AllHDUs) << "\n";
}
I've tested this on Wandbox using both clang & gcc HEAD with --pedantic-errors, and it compiles and returns the expected output, 7. That's not to say it's legal, just that it seems to be accepted by the compilers.
Is this explicitly documented behavior? I've been unable to parse the documentation in a way that describes this behavior.
[dcl.enum]/5:
... If the underlying type is fixed, the type of each enumerator prior to the closing brace is the underlying type ...
That is, each enumerator has type int until the closing brace is encountered. After that point, the enumerators have type FileCopy and you would not be able to bitwise-OR them together like this anymore.
According to the C++17 Standard (8.5.13 Bitwise inclusive OR operator)
1 The usual arithmetic conversions (8.3) are performed; the result is
the bitwise inclusive OR function of its operands. The operator
applies only to integral or unscoped enumeration operands.
And (10.2 Enumeration declarations)
... For a scoped enumeration type, the underlying type is int if it is not
explicitly specified. In both of these cases, the underlying
type is said to be fixed. Following the closing brace of an
enum-specifier, each enumerator has the type of its enumeration. If
the underlying type is fixed, the type of each enumerator prior to the
closing brace is the underlying type and the constant-expression in
the enumerator-definition shall be a converted constant expression of
the underlying type
So this is explicitly documented behavior.
C++ enumeration types appear to be "default constructible":
enum UE { a=1, b, c };
enum class SE { a=1, b, c };
int main() {
UE ue;
SE se;
}
How can this be explained from the standard?
I mean - let's say we wanted to change the standard to make it so they weren't default constructible. Which clauses would change?
It's all in [dcl.init]/7:
To default-initialize an object of type T means:
If T is a (possibly cv-qualified) class type, constructors are considered. The applicable constructors are enumerated
([over.match.ctor]), and the best one for the initializer () is chosen
through overload resolution. The constructor thus selected is called,
with an empty argument list, to initialize the object.
If T is an array type, each element is default-initialized.
Otherwise, no initialization is performed.
UE and SE match the third bullet, like fundamental types. So the initializaiton is simply a no-op, and they are left with an indeterminate value.
This is also the bullet list you'd need to tackle first to make enumerations not default initializable.
Don't let the class in enum class confuse you: It is still considered a non-class type. The syntax for scoped enums just happens to co-opt the class keyword, so as not to add yet another reserved word to the language.
There is a draft specification here: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/n4778.pdf
See section 9.6, Enumeration declarations.
I think you may be coming from Java, in which enumerations are classes. In C++, enumerated values are just integer constants. The type of the constant is generally int although it can be explicitly defined.
Since the enumeration is just an int, when you declare one without an initializer, there's no default constructor involved; you just get an uninitialized int.
Assuming there is an enum like this:
enum foo: int {
first,
second
}
Then I use it as follows:
foo f(1); // error: cannot initialize a variable of type 'foo' with an rvalue of type 'int'
foo f = foo(1); // OK !
I was wondering what is the difference between the two ?
I understand that the second version can be seen as a functional-style cast but why does this make any difference ?
For example, if I do this:
class Bar {};
Bar b = Bar(1); // no matching conversion for functional-style cast from 'int' to 'Bar'
I obviously get an error which makes sense. Therefore, this leads me to believe that in order for the second version of the foo example above to work there must be a conversion from int to enum defined somewhere but if there is such a conversion then why do I get an error in the first version ?
I do apologize if this is a duplicate. I am suspecting it is.
This seems relevant: Is this a cast or a construction?
... but not quit.
Thanks in advance !
Yes, the two forms are quite different, in a subtle way. Let's look at the first one, which results in an error. It's initialization of f, of type foo, from an int. It's described here, emphasis mine:
[dcl.init]/17.8
Otherwise, the initial value of the object being initialized is the
(possibly converted) value of the initializer expression. Standard
conversions will be used, if necessary, to convert the initializer
expression to the cv-unqualified version of the destination type; no
user-defined conversions are considered. If the conversion cannot be
done, the initialization is ill-formed.
The pertinent conversions in this case are integral conversions, mainly the one specified by the following:
[conv.integral]/1
A prvalue of an unscoped enumeration type can be converted to a
prvalue of an integer type.
So a an unscoped enumeration can be converted to an integer implicitly, but the converse is not true. Which is why the initialization is ill-formed. However, that functional-style cast notation is essentially a static cast. And a static cast can perform the inverse of (almost) any valid standard conversion. So the casted 1 is then used to initialize f, but at this point we are copy-initializing from a foo prvalue, which is of course perfectly fine.
Why does the code below compile without any errors?
enum class Enumeration;
void func()
{
auto enumeration = static_cast<Enumeration>(2);
auto value = static_cast<int>(enumeration);
}
It compiles because the compiler knows at compile time the size of Enumeration (which happens to be empty).
You see it explicitly using the following syntax:
enum class Enumeration : short;
The compiler knows everything there is to know about the Enumeration.
Enumeration is a opaque-enum-declaration which means also that the type is complete i.e. you can use sizeofon it. If needed you can specify the list of enumerators in a later redeclaration (unless the redeclaration comes with a different underlying type, obviously).
Note that since you are using enum class usage of static_cast is mandatory.
Strongly typed enum does not allow implicit conversion to int but you can safely use static_cast on them to retrieve their integral value.
They are still enum afterall.
Quoting cppreference
There are no implicit conversions from the values of a scoped
enumerator to integral types, although static_cast may be used to
obtain the numeric value of the enumerator.
More on this topic here: How to automatically convert strongly typed enum into int?
enum class E
{};
int main()
{
E e1{ 0 }; // ok
E e2 = 0; // not ok
// error : cannot initialize a variable of
// type 'E' with an rvalue of type 'int'
}
My compiler is clang 4.0 with option -std=c++1z.
It is expected that E e2 = 0; is not ok, because E is strongly-typed. However, what surprised me is that E e1{ 0 }; should be ok.
Why can a strongly-typed enum be initialized with an integer without static_cast?
Looking at the reference using list intializers is allowed since C++17:
Both scoped enumeration types and unscoped enumeration types whose
underlying type is fixed can be initialized from an integer without a
cast, using list initialization, if all of the following is true:
the initialization is direct-list-initialization
the initializer list has only a single element
the enumeration is either scoped or unscoped with underlying type fixed
the conversion is non-narrowing
Clang supports this since version 3.9 (according to the implementation status page)
GCC supports this since version 7 (according to the standards support page)
See this C++ proposal for additional context and motivation: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0138r2.pdf