Coming Soon
This lesson is currently being developed
Passing and returning structs
Use structs as function parameters and return values.
What to Expect
Comprehensive explanations with practical examples
Interactive coding exercises to practice concepts
Knowledge quiz to test your understanding
Step-by-step guidance for beginners
Development Status
Content is being carefully crafted to provide the best learning experience
Preview
Early Preview Content
This content is still being developed and may change before publication.
13.10 — Passing and returning structs
In this lesson, you'll learn how to pass structs to functions and return structs from functions, enabling you to work with structured data efficiently in your programs.
Passing structs to functions
Just like with fundamental types, you can pass structs to functions as parameters. There are several ways to do this, each with different performance and behavior characteristics.
Passing structs by value
When you pass a struct by value, a copy of the entire struct is made:
#include <iostream>
struct Point
{
int x{0};
int y{0};
};
void printPoint(Point p) // Pass by value - creates a copy
{
std::cout << "Point: (" << p.x << ", " << p.y << ")" << std::endl;
}
void modifyPoint(Point p) // Pass by value - modifies the copy
{
p.x = 99;
p.y = 99;
std::cout << "Inside modifyPoint: (" << p.x << ", " << p.y << ")" << std::endl;
}
int main()
{
Point origin{3, 5};
printPoint(origin); // Pass a copy to printPoint
modifyPoint(origin); // Pass a copy to modifyPoint
// Original struct is unchanged
std::cout << "After modifyPoint: (" << origin.x << ", " << origin.y << ")" << std::endl;
return 0;
}
Output:
Point: (3, 5)
Inside modifyPoint: (99, 99)
After modifyPoint: (3, 5)
Notice that the original origin
struct wasn't modified because modifyPoint
only changed the copy.
Passing structs by const reference (recommended)
For better performance, especially with large structs, pass by const reference when you don't need to modify the struct:
#include <iostream>
#include <string>
struct Employee
{
int id{0};
std::string name{"Unknown"};
std::string department{"General"};
double salary{0.0};
};
void printEmployee(const Employee& emp) // Pass by const reference
{
std::cout << "Employee #" << emp.id << std::endl;
std::cout << "Name: " << emp.name << std::endl;
std::cout << "Department: " << emp.department << std::endl;
std::cout << "Salary: $" << emp.salary << std::endl;
}
double calculateAnnualTax(const Employee& emp) // Pass by const reference
{
return emp.salary * 0.25; // 25% tax rate
}
int main()
{
Employee alice{1001, "Alice Johnson", "Engineering", 85000.0};
printEmployee(alice); // No copy made - efficient!
double tax = calculateAnnualTax(alice);
std::cout << "Annual tax: $" << tax << std::endl;
return 0;
}
Output:
Employee #1001
Name: Alice Johnson
Department: Engineering
Salary: $85000
Annual tax: $21250
Passing structs by reference for modification
When you need to modify the original struct, pass by reference:
#include <iostream>
#include <string>
struct BankAccount
{
int accountNumber{0};
std::string ownerName{"Unknown"};
double balance{0.0};
};
void deposit(BankAccount& account, double amount) // Pass by reference to modify
{
if (amount > 0)
{
account.balance += amount;
std::cout << "Deposited $" << amount << " into account "
<< account.accountNumber << std::endl;
}
}
void withdraw(BankAccount& account, double amount) // Pass by reference to modify
{
if (amount > 0 && amount <= account.balance)
{
account.balance -= amount;
std::cout << "Withdrew $" << amount << " from account "
<< account.accountNumber << std::endl;
}
else
{
std::cout << "Insufficient funds or invalid amount" << std::endl;
}
}
void printBalance(const BankAccount& account) // Pass by const reference to read
{
std::cout << account.ownerName << "'s account balance: $"
<< account.balance << std::endl;
}
int main()
{
BankAccount aliceAccount{12345, "Alice Johnson", 1000.0};
printBalance(aliceAccount);
deposit(aliceAccount, 500.0);
printBalance(aliceAccount);
withdraw(aliceAccount, 200.0);
printBalance(aliceAccount);
withdraw(aliceAccount, 2000.0); // Should fail
printBalance(aliceAccount);
return 0;
}
Output:
Alice Johnson's account balance: $1000
Deposited $500 into account 12345
Alice Johnson's account balance: $1500
Withdrew $200 from account 12345
Alice Johnson's account balance: $1300
Insufficient funds or invalid amount
Alice Johnson's account balance: $1300
Returning structs from functions
Functions can return structs just like any other type. This is useful for creating and initializing structs or performing calculations that produce structured results.
Basic struct return
#include <iostream>
#include <string>
struct Rectangle
{
double width{0.0};
double height{0.0};
};
Rectangle createRectangle(double w, double h)
{
return Rectangle{w, h}; // Return a struct using aggregate initialization
}
Rectangle createSquare(double side)
{
return {side, side}; // Implicit type - compiler knows we're returning Rectangle
}
double calculateArea(const Rectangle& rect)
{
return rect.width * rect.height;
}
int main()
{
Rectangle room = createRectangle(12.0, 8.0);
Rectangle tile = createSquare(2.0);
std::cout << "Room: " << room.width << "x" << room.height
<< ", Area: " << calculateArea(room) << std::endl;
std::cout << "Tile: " << tile.width << "x" << tile.height
<< ", Area: " << calculateArea(tile) << std::endl;
return 0;
}
Output:
Room: 12x8, Area: 96
Tile: 2x2, Area: 4
Returning structs from calculations
#include <iostream>
#include <cmath>
struct Point
{
double x{0.0};
double y{0.0};
};
struct Vector
{
double magnitude{0.0};
double angle{0.0}; // in radians
};
Point addPoints(const Point& p1, const Point& p2)
{
return Point{p1.x + p2.x, p1.y + p2.y};
}
Vector pointToVector(const Point& p)
{
double mag = std::sqrt(p.x * p.x + p.y * p.y);
double ang = std::atan2(p.y, p.x);
return Vector{mag, ang};
}
Point vectorToPoint(const Vector& v)
{
double x = v.magnitude * std::cos(v.angle);
double y = v.magnitude * std::sin(v.angle);
return Point{x, y};
}
int main()
{
Point p1{3.0, 4.0};
Point p2{1.0, 2.0};
Point sum = addPoints(p1, p2);
std::cout << "Sum: (" << sum.x << ", " << sum.y << ")" << std::endl;
Vector v = pointToVector(p1);
std::cout << "Vector from p1: magnitude=" << v.magnitude
<< ", angle=" << v.angle << " radians" << std::endl;
Point reconstructed = vectorToPoint(v);
std::cout << "Reconstructed point: (" << reconstructed.x
<< ", " << reconstructed.y << ")" << std::endl;
return 0;
}
Output:
Sum: (4, 6)
Vector from p1: magnitude=5, angle=0.927295 radians
Reconstructed point: (3, 4)
Practical example: Student grade management
Let's create a comprehensive example that demonstrates passing and returning structs:
#include <iostream>
#include <string>
#include <vector>
struct Grade
{
std::string subject{"Unknown"};
double points{0.0};
int creditHours{0};
};
struct Student
{
int id{0};
std::string name{"Unknown"};
std::vector<Grade> grades;
};
struct GPA_Result
{
double gpa{0.0};
int totalCreditHours{0};
bool isValid{false};
};
void addGrade(Student& student, const Grade& grade) // Pass student by reference, grade by const reference
{
student.grades.push_back(grade);
std::cout << "Added grade: " << grade.subject << " ("
<< grade.points << " points, " << grade.creditHours
<< " credit hours)" << std::endl;
}
Grade createGrade(const std::string& subject, double points, int creditHours) // Return a Grade
{
return Grade{subject, points, creditHours};
}
GPA_Result calculateGPA(const Student& student) // Return a GPA_Result struct
{
if (student.grades.empty())
{
return GPA_Result{0.0, 0, false}; // Invalid GPA
}
double totalGradePoints = 0.0;
int totalCredits = 0;
for (const Grade& grade : student.grades)
{
totalGradePoints += grade.points * grade.creditHours;
totalCredits += grade.creditHours;
}
if (totalCredits == 0)
{
return GPA_Result{0.0, 0, false};
}
double gpa = totalGradePoints / totalCredits;
return GPA_Result{gpa, totalCredits, true};
}
void printStudentReport(const Student& student) // Pass by const reference
{
std::cout << "\n=== Student Report ===" << std::endl;
std::cout << "ID: " << student.id << std::endl;
std::cout << "Name: " << student.name << std::endl;
if (student.grades.empty())
{
std::cout << "No grades recorded." << std::endl;
return;
}
std::cout << "Grades:" << std::endl;
for (const Grade& grade : student.grades)
{
std::cout << " " << grade.subject << ": " << grade.points
<< " points (" << grade.creditHours << " credit hours)" << std::endl;
}
GPA_Result result = calculateGPA(student);
if (result.isValid)
{
std::cout << "GPA: " << result.gpa << std::endl;
std::cout << "Total Credit Hours: " << result.totalCreditHours << std::endl;
}
else
{
std::cout << "GPA: Not calculated (invalid data)" << std::endl;
}
}
int main()
{
Student alice{1001, "Alice Johnson"};
// Add grades using functions that pass and return structs
addGrade(alice, createGrade("Mathematics", 3.7, 4));
addGrade(alice, createGrade("Physics", 3.9, 3));
addGrade(alice, createGrade("Computer Science", 4.0, 4));
addGrade(alice, createGrade("English", 3.5, 3));
printStudentReport(alice);
return 0;
}
Output:
Added grade: Mathematics (3.7 points, 4 credit hours)
Added grade: Physics (3.9 points, 3 credit hours)
Added grade: Computer Science (4 points, 4 credit hours)
Added grade: English (3.5 points, 3 credit hours)
=== Student Report ===
ID: 1001
Name: Alice Johnson
Grades:
Mathematics: 3.7 points (4 credit hours)
Physics: 3.9 points (3 credit hours)
Computer Science: 4 points (4 credit hours)
English: 3.5 points (3 credit hours)
GPA: 3.73571
Total Credit Hours: 14
Performance considerations
When to pass by value vs. reference
#include <iostream>
#include <string>
// Small struct - passing by value is fine
struct Point
{
double x, y; // 16 bytes total
};
// Large struct - prefer passing by reference
struct LargeStruct
{
double data[1000]; // 8000 bytes
std::string description; // Additional bytes
int metadata[100]; // 400 more bytes
};
void processSmall(Point p) // OK: small struct, copy is cheap
{
std::cout << "Processing point (" << p.x << ", " << p.y << ")" << std::endl;
}
void processLarge(const LargeStruct& ls) // Better: avoid expensive copy
{
std::cout << "Processing large struct: " << ls.description << std::endl;
}
int main()
{
Point pt{3.0, 4.0};
processSmall(pt); // Copying 16 bytes is fine
LargeStruct big{}; // Imagine this is filled with data
big.description = "Big data structure";
processLarge(big); // Avoid copying thousands of bytes
return 0;
}
Output:
Processing point (3, 4)
Processing large struct: Big data structure
Common patterns for struct parameters
Builder pattern with structs
#include <iostream>
#include <string>
struct Car
{
std::string make{"Unknown"};
std::string model{"Unknown"};
int year{0};
std::string color{"White"};
double price{0.0};
};
Car buildCar(const std::string& make, const std::string& model, int year)
{
return Car{make, model, year, "White", 0.0}; // Default color and price
}
Car setColor(Car car, const std::string& color) // Pass by value to avoid modifying original
{
car.color = color;
return car;
}
Car setPrice(Car car, double price)
{
car.price = price;
return car;
}
void displayCar(const Car& car)
{
std::cout << car.year << " " << car.make << " " << car.model
<< " (" << car.color << ") - $" << car.price << std::endl;
}
int main()
{
Car myCar = buildCar("Toyota", "Camry", 2023);
myCar = setColor(myCar, "Blue");
myCar = setPrice(myCar, 28500.0);
displayCar(myCar);
// Or chain the operations
Car anotherCar = setPrice(setColor(buildCar("Honda", "Civic", 2022), "Red"), 25000.0);
displayCar(anotherCar);
return 0;
}
Output:
2023 Toyota Camry (Blue) - $28500
2022 Honda Civic (Red) - $25000
Best practices for passing and returning structs
1. Choose the right parameter type
// Read-only access to large struct: const reference
void analyzeData(const LargeDataStruct& data) { /* ... */ }
// Modify struct: reference
void updateData(LargeDataStruct& data) { /* ... */ }
// Small struct or when you need a copy: value
Point translatePoint(Point p, double dx, double dy) { /* ... */ }
2. Use meaningful return types
// Good: struct conveys multiple related values
struct Division_Result
{
double quotient;
double remainder;
bool isValid;
};
Division_Result divide(double a, double b);
// Less clear: multiple unrelated return parameters
std::pair<double, double> divide(double a, double b); // What's first? What's second?
3. Initialize returned structs properly
// Good: always return fully initialized structs
Rectangle createRectangle(double w, double h)
{
if (w <= 0 || h <= 0)
{
return Rectangle{0.0, 0.0}; // Safe default
}
return Rectangle{w, h};
}
// Be consistent with error handling
std::optional<Rectangle> createValidRectangle(double w, double h)
{
if (w <= 0 || h <= 0)
{
return std::nullopt; // Indicate failure
}
return Rectangle{w, h};
}
Key concepts to remember
-
Structs can be passed by value, reference, or const reference just like other types.
-
Pass by const reference for read-only access to avoid expensive copying.
-
Pass by reference when you need to modify the original struct.
-
Functions can return structs to provide structured results from calculations.
-
Consider performance - large structs should generally be passed by reference.
-
Use aggregate initialization when returning structs for clean, readable code.
Summary
Passing and returning structs enables you to work with complex data efficiently in functions. By choosing the appropriate parameter types (value, reference, or const reference), you can balance performance with clarity and safety. Functions that return structs provide a clean way to package related results and make your code more expressive. Understanding these concepts allows you to design better interfaces and create more maintainable programs that work effectively with structured data.
Quiz
- What's the difference between passing a struct by value versus by const reference?
- When should you pass a struct by reference versus by const reference?
- How do you return a struct from a function using aggregate initialization?
- Why might passing large structs by value be a performance problem?
- What are some advantages of returning structs from functions instead of using output parameters?
Practice exercises
Try these exercises with struct parameters and return values:
- Create a
Temperature
struct and functions to convert between Celsius, Fahrenheit, and Kelvin. Practice passing structs and returning converted results. - Create a
Circle
struct and functions that take circles by const reference to calculate area, circumference, and check if a point is inside the circle. - Create a
Time
struct and functions to add/subtract time values, returning new Time structs with the results. - Design a simple inventory system with
Product
structs and functions that pass products by reference to modify quantities, and return search results.
Explore More Courses
Discover other available courses while this lesson is being prepared.
Browse CoursesLesson Discussion
Share your thoughts and questions