Chapter 18: Enums and Structs

Program-Defined Types

A program-defined type used in only one code file should be defined in that code file as close to the first point of use as possible. A program-defined type used in multiple code files should be defined in a header file with the same name as the program-defined type and then #included into each code file as needed.

Enumerations

Unscoped Enumerations

enum Color
{
    red,         // 0
    green,       // 1
    yellow = -3, // -3
    blue,        // trailing comma optional but recommended
}; // the enum definition must end with a semicolon

enum Animal
{
    cat = -3,    // values can be negative
    dog,         // -2
    pig,         // -1
    horse = 5,
    giraffe = 5, // shares same value as horse
    chicken,     // 6
};

Color apple { red };
Color raspberry { Color::red }; // accessing enumerator from scope of Color

Animal a {}; // value-initialization zero-initializes a to value 0

if (shirt == blue) // if the shirt is blue
    std::cout << "Your shirt is blue!";

Scoped Enumerations / Enum Class

enum class Color // "enum class" defines this as a scoped enumeration rather than an unscoped enumeration
{
    red, // red is considered part of Color's scope region
};

enum class Fruit
{
    banana, // banana is considered part of Fruit's scope region
};

Color color { Color::red };    // note: red is not directly accessible, we have to use Color::red
Fruit fruit { Fruit::banana };

if (color == fruit) // compile error: the compiler doesn't know how to compare different types Color and Fruit
    std::cout << "color and fruit are equal\n";

std::cout << static_cast<int>(color) << '\n';   // explicit conversion to int, will print 1

Using Enumerations

#include <optional> // for std::optional

enum Pet
{
    cat,   // 0
    dog,   // 1
};

constexpr std::optional<Pet> getPetFromString(std::string_view sv)
{
    if (sv == "cat")   return cat;
    if (sv == "dog")   return dog;
    return {};
}

std::optional<Pet> pet { getPetFromString(s) };

if (!pet)
    std::cout << "You entered an Invalid pet\n";

Scoped Enumerators Int Conversion

#include <type_traits> // for std::underlying_type_t
constexpr auto operator+(Animals a) constexpr
{
    return static_cast<std::underlying_type_t<Animals>>(a);
}
std::cout << +Animals::elephant << '\n'; // convert Animals::elephant to an integer using unary operator

Structs

Structs are user-defined types that group variables of different types together.

Basic Struct

struct Employee
{
    int id;            // no value-initialized, bad
    int age {};        // value-initialized by default, 0
    double wage {232}; // explicit default value
};

void printEmployee(const Employee& employee) // note pass by reference here
{
    std::cout << "ID:   " << employee.id << '\n';
    std::cout << "Age:  " << employee.age << '\n';
    std::cout << "Wage: " << employee.wage << '\n';
}

Employee joe {};
Employee frank = { 1, 32, 60000.0 }; // copy-list initialization using braced list
Employee joe { 2, 28, 45000.0 };     // list initialization using braced list (preferred)
Employee joe { 2, 28 }; // joe.wage will be value-initialized to 0.0
Employee joe {}; // value-initialize all members

printEmployee(Employee { 14, 32, 24.15 }); // construct a temporary Employee to pass to function (type explicitly specified) (preferred)
printEmployee({ 15, 28, 18.27 });          // construct a temporary Employee to pass to function (type deduced from parameter)

Nested Struct

struct Company
{
    struct Employee // accessed via Company::Employee
    {
        int id{};
        int age{};
        double wage{};
    };

    int numberOfEmployees{};
    Employee CEO{}; // Employee is a struct within the Company struct
};

Company myCompany{ 7, { 1, 32, 55000.0 } }; // Nested initialization list to initialize Employee
myCompany.CEO.wage;

Returning Structs

struct Point3d
{
    double x { 0.0 };
    double y { 0.0 };
    double z { 0.0 };
};

Point3d getZeroPoint()
{
    return Point3d { 0.0, 0.0, 0.0 }; // return an unnamed Point3d
}

Point3d getZeroPoint()
{
    return { 0.0, 0.0, 0.0 }; // return an unnamed Point3d
}

Struct Size and Padding

The size of a struct will be at least as large as the size of all the variables it contains. You can minimize padding by defining your members in decreasing order of size.

struct Foo1
{
    short a{}; // will have 2 bytes of padding after a
    int b{};
    short c{}; // will have 2 bytes of padding after c
};

struct Foo2
{
    int b{};
    short a{};
    short c{};
};

sizeof(Foo1); // 12