Skip to content
Laviyx Learn

Try

C++ course menu

C++ Structs and Enums: Grouping Data and Naming Choices

Learn C++ structs to group related values into one type, aggregate initialization, vectors of structs, passing structs to functions, structured bindings, and enums: plain enum vs enum class, underlying values and switching on enums.

Beginner13 min readUpdated
On this page

So far each variable has held one value. Real data comes in groups: a student has a name, an age and marks; a point has an x and a y. A struct bundles related values into one new type. An enum gives names to a fixed set of choices, such as the days of the week or the status of an order.

Defining a struct#

C++
#include <iostream>
#include <string>

struct Student {
    std::string name;
    int age = 0;
    double marks = 0.0;
};

int main() {
    Student s;
    s.name = "Asha";
    s.age = 20;
    s.marks = 91.5;

    std::cout << s.name << " (" << s.age << ") scored " << s.marks << '\n';
}
Output
Asha (20) scored 91.5
  • struct Student { ... }; defines a new type. Note the semicolon after the closing brace.
  • Each variable inside is a member (or field). Access members with the dot: s.name.
  • int age = 0; gives a default value, used when you do not set the member.

Initializing a struct#

Instead of setting members one by one, list the values in braces, in the order the members are declared:

C++
#include <iostream>
#include <string>

struct Point {
    double x = 0;
    double y = 0;
};

struct Product {
    std::string name;
    double price = 0;
    int stock = 0;
};

int main() {
    Point origin;                        // defaults: 0, 0
    Point p{3.5, -2};
    Product pen{"Pen", 25.0, 120};
    Product book{.name = "Notebook", .price = 60};   // C++20 designated initializers

    std::cout << origin.x << ',' << origin.y << "  " << p.x << ',' << p.y << '\n';
    std::cout << pen.name << ' ' << pen.stock << "  " << book.name << ' ' << book.stock << '\n';
}
Output
0,0  3.5,-2
Pen 120  Notebook 0

C++20 designated initializers (.name = ...) name each member, which is clearer for structs with many fields. They must still appear in declaration order, and members you skip keep their defaults.

Vectors of structs#

Structs become powerful when you store many of them:

C++
#include <iostream>
#include <string>
#include <vector>

struct Product {
    std::string name;
    double price = 0;
    int stock = 0;
};

int main() {
    std::vector<Product> inventory = {
        {"Laptop", 55000, 12},
        {"Headphones", 2500, 0},
        {"Keyboard", 1800, 25},
    };

    double value = 0;
    for (const Product& p : inventory) {
        if (p.stock == 0) {
            std::cout << p.name << " is out of stock\n";
        }
        value += p.price * p.stock;
    }
    std::cout << "Stock value: " << value << '\n';
}
Output
Headphones is out of stock
Stock value: 705000

Structs and functions#

Pass structs by const reference to read them, and return them by value:

C++
#include <cmath>
#include <iostream>

struct Point {
    double x = 0;
    double y = 0;
};

double distance(const Point& a, const Point& b) {
    double dx = a.x - b.x;
    double dy = a.y - b.y;
    return std::sqrt(dx * dx + dy * dy);
}

Point midpoint(const Point& a, const Point& b) {
    return {(a.x + b.x) / 2, (a.y + b.y) / 2};
}

int main() {
    Point a{0, 0};
    Point b{6, 8};

    std::cout << distance(a, b) << '\n';
    auto [mx, my] = midpoint(a, b);
    std::cout << mx << ' ' << my << '\n';
}
Output
10
3 4

auto [mx, my] = ... is a structured binding: it unpacks the struct's members, in order, into new variables.

Comparing structs#

By default, == does not work on your own structs. C++20 can generate the comparison for you:

C++
#include <iostream>

struct Version {
    int major = 0;
    int minor = 0;
    bool operator==(const Version&) const = default;
    auto operator<=>(const Version&) const = default;
};

int main() {
    Version a{2, 1};
    Version b{2, 3};
    std::cout << std::boolalpha << (a == b) << ' ' << (a < b) << '\n';
}
Output
false true

The defaulted <=> ("spaceship") operator compares members in order, giving you <, >, <= and >=. You will learn how operators work in Operator Overloading.

struct vs class#

In C++, a struct and a class are almost identical; the only difference is that struct members are public by default and class members are private by default. By convention, use struct for simple bundles of data with no rules to protect, and class when the type has behaviour and invariants to maintain. See Classes and Objects.

Enums: naming a set of choices#

An enumeration defines a type whose values are a fixed list of names. Modern C++ uses enum class (a "scoped enum"):

C++
#include <iostream>

enum class OrderStatus { Pending, Shipped, Delivered, Cancelled };

const char* to_text(OrderStatus s) {
    switch (s) {
        case OrderStatus::Pending:   return "Pending";
        case OrderStatus::Shipped:   return "Shipped";
        case OrderStatus::Delivered: return "Delivered";
        case OrderStatus::Cancelled: return "Cancelled";
    }
    return "Unknown";
}

int main() {
    OrderStatus status = OrderStatus::Shipped;

    if (status == OrderStatus::Shipped) {
        std::cout << "On its way!\n";
    }
    std::cout << to_text(status) << '\n';
}
Output
On its way!
Shipped

Using OrderStatus::Shipped instead of a number like 1 makes the code self-explanatory, and the compiler stops you from assigning an invalid value.

Underlying values#

Each enumerator has an integer value, starting from 0 unless you choose:

C++
#include <iostream>

enum class Priority { Low = 1, Medium = 5, High = 10 };

int main() {
    Priority p = Priority::High;
    std::cout << static_cast<int>(p) << '\n';
}
Output
10

An enum class does not convert to int automatically; you need static_cast. That strictness prevents mistakes like adding a priority to a day of the week.

Plain enum vs enum class#

Older code uses plain enum, whose names spill into the surrounding scope and convert silently to int:

C++
enum Color { Red, Green, Blue };       // plain enum: 'Red' is a global name
enum class Fruit { Apple, Banana };    // scoped: write Fruit::Apple

With plain enums, two enums cannot both have a Red, and Red + 1 compiles without complaint. Prefer enum class in new code.

Common mistakes#

Practice#

Practice exercise

Define enum class Level { Beginner, Intermediate, Advanced } and struct Course { std::string title; int lessons; Level level; }. Store three courses in a vector, and print the titles of the courses that are not Beginner, plus the total number of lessons.

Show solution
C++
#include <iostream>
#include <string>
#include <vector>

enum class Level { Beginner, Intermediate, Advanced };

struct Course {
    std::string title;
    int lessons = 0;
    Level level = Level::Beginner;
};

int main() {
    std::vector<Course> courses = {
        {"C++ Basics", 17, Level::Beginner},
        {"Pointers and Memory", 4, Level::Intermediate},
        {"Templates", 7, Level::Advanced},
    };

    int total = 0;
    for (const Course& c : courses) {
        total += c.lessons;
        if (c.level != Level::Beginner) {
            std::cout << c.title << '\n';
        }
    }
    std::cout << "Total lessons: " << total << '\n';
}
Output
Pointers and Memory
Templates
Total lessons: 28

Key takeaway#

Finished this lesson?