cpp11 language types

Save to:
Instapaper Pocket Readability

C++11 Language Extensions — Other Types

enum class

The enum classes (“new enums”, “strong enums”) address three problems with traditional C++ enumerations:

  • Conventional enums implicitly convert to an integer, causing errors when someone does not want an enumeration to act as an integer.
  • Conventional enums 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 classes (“strong enums”) 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 enums work as usual, but you can now optionally qualify enumerators with the enum name

The new enums 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:

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 longs nor can long be spelled short long long.

See also:

Extended integer types

There is a set of rules for how an extended (precision) integer type should behave if one exists.

See

Generalized unions

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:

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: