C++11 Language Extensions — Other Types
enum class
The enum class
es (“new enum
s”, “strong enum
s”) address three problems with traditional C++ enumerations:
- Conventional
enum
s implicitly convert to an integer, causing errors when someone does not want an enumeration to act as an integer. - Conventional
enum
s export their enumerators to the surrounding scope, causing name clashes. - The underlying type of an
enum
cannot be specified, causing confusion, compatibility problems, and makes forward declaration impossible.
enum class
es (“strong enum
s”) are strongly typed and scoped:
enum Alert { green, yellow, orange, red }; // traditional enum
enum class Color { red, blue }; // scoped and strongly typed enum
// no export of enumerator names into enclosing scope
// no implicit conversion to int
enum class TrafficLight { red, yellow, green };
Alert a = 7; // error (as ever in C++)
Color c = 7; // error: no int->Color conversion
int a2 = red; // ok: Alert->int conversion
int a3 = Alert::red; // error in C++98; ok in C++11
int a4 = blue; // error: blue not in scope
int a5 = Color::blue; // error: not Color->int conversion
Color a6 = Color::blue; // ok
As shown, traditional enum
s work as usual, but you can now optionally qualify enumerators with the enum
name
The new enum
s are “enum class
” because they combine aspects of traditional enumerations (names values) with aspects of classes (scoped members and absence of conversions). This is the same name for this feature as when it was originally designed in C++/CLI before being proposed for ISO C++.
Being able to specify the underlying type allows simpler interoperability and guaranteed sizes of enumerations:
enum class Color : char { red, blue }; // compact representation
enum class TrafficLight { red, yellow, green }; // by default, the underlying type is int
enum E { E1 = 1, E2 = 2, Ebig = 0xFFFFFFF0U }; // how big is an E?
// (whatever the old rules say;
// i.e. "implementation defined")
enum EE : unsigned long { EE1 = 1, EE2 = 2, EEbig = 0xFFFFFFF0U }; // now we can be specific
It also enables forward declaration of enums:
enum class Color_code : char; // (forward) declaration
void foobar(Color_code* p); // use of forward declaration
// ...
enum class Color_code : char { red, yellow, green, blue }; // definition
The underlying type must be one of the signed or unsigned integer types; the default is int
.
In the standard library, enum classes are used
- For mapping systems specific error codes: In : enum class errc;
- For pointer safety indicators: In : enum class pointer_safety { relaxed, preferred, strict };
- For I/O stream errors: In : enum class io_errc { stream = 1 };
- For asynchronous communications error handling: In : enum class future_errc { broken_promise, future_already_retrieved, promise_already_satisfied };
Several of these have operators such as ==
defined.
See also:
- the C++ draft section 7.2
- [N1513=03-0096] David E. Miller: Improving Enumeration Types (original enum proposal).
- [N2347 = J16/07-0207] David E. Miller, Herb Sutter, and Bjarne Stroustrup: Strongly Typed Enums (revision 3).
- [N2499=08-0009] Alberto Ganesh Barbati: Forward declaration of enumerations.
long long
– a longer integer
An integer that’s at least 64 bits long. For example:
long long x = 9223372036854775807LL;
No, there are no long long long
s nor can long
be spelled short long long
.
See also:
- [05-0071==N1811] J. Stephen Adamczyk: Adding the
long long
type to C++ (Revision 3).
Extended integer types
There is a set of rules for how an extended (precision) integer type should behave if one exists.
See
- [06-0058==N1988] J. Stephen Adamczyk: Adding extended integer types to C++ (Revision 1).
Generalized union
s
In C++98 (as in the earlier pre-Standard versions of C++), a member with a user-defined constructor, destructor, or assignment cannot be a member of a union
:
union U {
int m1;
complex<double> m2; // error (silly): complex has constructor
string m3; // error (not silly): string has a serious invariant
// maintained by ctor, copy, and dtor
};
In particular
U u; // which constructor, if any?
u.m1 = 1; // assign to int member
string s = u.m3; // disaster: read from string member
Obviously, it’s illegal to write one member and then read another but people do that nevertheless (usually by mistake).
C++11 modifies the restrictions of unions to make more member types feasible; in particular, it allows a member of types with constructors and destructors. It also adds a restriction to make the more flexible unions less error-prone by encouraging the building of discriminated unions.
Union member types are restricted:
- No virtual functions (as ever)
- No references (as ever)
- No bases (as ever)
- If a union has a member with a user-defined constructor, copy, or destructor then that special function is deleted; that is, it cannot be used for an object of the union type. This is new.
For example:
union U1 {
int m1;
complex<double> m2; // ok
};
union U2 {
int m1;
string m3; // ok
};
This may look error-prone, but the new restriction helps. In particular:
U1 u; // ok
u.m2 = {1,2}; // ok: assign to the complex member
U2 u2; // error: the string destructor caused the U destructor to be deleted
U2 u3 = u2; // error: the string copy constructor caused the U copy constructor to be deleted
Basically, U2
is useless unless you embed it in a struct that keeps track of which member (variant) is used. So, build discriminated unions, such as:
class Widget { // Three alternative implementations represented as a union
private:
enum class Tag { point, number, text } type; // discriminant
union { // representation
point p; // point has constructor
int i;
string s; // string has default constructor, copy operations, and destructor
};
// ...
widget& operator=(const widget& w) // necessary because of the string variant
{
if (type==Tag::text && w.type==Tag::text) {
s = w.s; // usual string assignment
return *this;
}
if (type==Tag::text) s.~string(); // destroy (explicitly!)
switch (w.type) {
case Tag::point: p = w.p; break; // normal copy
case Tag::number: i = w.i; break;
case Tag::text: new(&s)(w.s); break; // placement new
}
type = w.type;
return *this;
}
};
See also:
- the C++ draft section 9.5
- [N2544=08-0054] Alan Talbot, Lois Goldthwaite, Lawrence Crowl, and Jens Maurer: Unrestricted unions (Revision 2)
Generalized PODs
A POD (“Plain Old Data”) is something that can be manipulated like a C struct, e.g. bitwise copyable with memcpy()
, bitwise initializable with memset()
, etc. In C++98 the actual definition of POD is based on a set of restrictions on the use of language features used in the definition of a struct:
// S is a POD
struct S {
int a;
};
// SS was not a POD in C++98, is a POD in C++11
struct SS {
int a;
SS(int aa) : a(abs(aa)) { assert(a>=0); }
};
// Definitely not POD
struct SSS {
virtual void f(); /* ... */
};
In C++11, S
and SS
are “standard layout types” (a superset of “POD types”) because there is really nothing “magic” about SS: The constructor does not affect the layout (so memcpy()
would be fine), only the initialization rules do (memset()
would be bad – not enforcing any invariant the type might have, such as a >= 0
). However, SSS
will still have an embedded vptr and will not be anything like “plain old data.” C++11 defines POD types, trivially copyable types, trivial types, and standard-layout types to deal with various technical aspects of what used to be PODs. POD is defined recursively:
- If all your members and bases are PODs, you’re a POD
- As usual (details in section 9 [10])
- No virtual functions
- No virtual bases
- No references
- No multiple access specifiers
The most important aspect of C++11 PODs is that adding or subtracting constructors do not affect layout or performance.
See also:
- the C++ draft section 3.9 and 9 [10]
- [N2294=07-0154] Beman Dawes: POD’s Revisited; Resolving Core Issue 568 (Revision 4).