Menu
Coddy logo textTech

Upcasting

Part of the Object Oriented Programming section of Coddy's C journey — lesson 31 of 61.

Upcasting is the act of treating a specific (derived) type as its more general (base) type. In C's composition model, this means passing a pointer to a struct that embeds a base type to a function expecting the base type.

Consider a Vehicle base struct and a Car that contains it:

typedef struct {
    int speed;
} Vehicle;

typedef struct {
    Vehicle vehicle;  // Base as first member
    int doors;
} Car;

Now imagine a generic function that works with any vehicle:

void accelerate(Vehicle* v, int amount) {
    v->speed += amount;
}

Thanks to the first member rule, we can pass a Car* to this function by casting it to Vehicle*:

Car my_car = {{0}, 4};  // speed=0, doors=4
accelerate((Vehicle*)&my_car, 50);
printf("Speed: %d\n", my_car.vehicle.speed);  // Output: 50

This is upcasting — we're moving "up" from a specific type to a general one. The function accelerate doesn't know it's working with a Car; it only sees a Vehicle. This allows you to write reusable code that operates on the base type while accepting any derived type that embeds it.

challenge icon

Challenge

Easy

Let's build a simple transportation system that demonstrates upcasting in action. You'll create a base Vehicle type and a more specific Motorcycle type, then show how a generic function can work with both through pointer casting.

The power of upcasting is that you can write functions for the base type, and they'll work seamlessly with any derived type that embeds it. Your motorcycle "is a" vehicle, so any function expecting a Vehicle* should be able to process it.

You'll organize your code across three files:

  • vehicle.h: Define your struct hierarchy with include guards. Create a Vehicle struct with two fields: speed (an integer for current speed) and max_speed (an integer for the vehicle's top speed). Then define a Motorcycle struct that embeds Vehicle as its first member and adds a has_sidecar field (an integer, where 1 means yes and 0 means no). Declare two functions: boost that takes a Vehicle* and an amount to increase speed, and print_motorcycle that takes a Motorcycle*.
  • vehicle.c: Implement both functions. Your boost function should increase the vehicle's speed by the given amount, but cap it at max_speed—if the new speed would exceed the maximum, set it to max_speed instead. Your print_motorcycle function should display all the motorcycle's information including its embedded vehicle data.
  • main.c: Create a Motorcycle and initialize all its fields. Then demonstrate upcasting by calling the boost function—cast your Motorcycle* to a Vehicle* to pass it to this generic function. After boosting, print the motorcycle's information to see the result.

You will receive four inputs: the motorcycle's initial speed, its max speed, whether it has a sidecar (1 or 0), and the boost amount.

Your output should look like this:

Speed: 75
Max Speed: 120
Sidecar: Yes

Where 75 is the speed after boosting (initial 50 + boost 25), 120 is the max speed, and Yes or No indicates whether the motorcycle has a sidecar. If the boost would push speed beyond max_speed, the speed should be capped at max_speed.

The key learning here is that boost knows nothing about motorcycles—it only works with Vehicle. Yet through upcasting, your motorcycle can use this generic function to modify its embedded vehicle data.

Cheat sheet

Upcasting treats a specific (derived) type as its more general (base) type by passing a pointer to a struct that embeds a base type to a function expecting the base type.

Define a base struct and a derived struct that embeds it as the first member:

typedef struct {
    int speed;
} Vehicle;

typedef struct {
    Vehicle vehicle;  // Base as first member
    int doors;
} Car;

Create a function that works with the base type:

void accelerate(Vehicle* v, int amount) {
    v->speed += amount;
}

Cast a derived type pointer to the base type pointer to call the function:

Car my_car = {{0}, 4};  // speed=0, doors=4
accelerate((Vehicle*)&my_car, 50);
printf("Speed: %d\n", my_car.vehicle.speed);  // Output: 50

This allows you to write reusable code that operates on the base type while accepting any derived type that embeds it.

Try it yourself

#include <stdio.h>
#include "vehicle.h"

int main() {
    int initial_speed, max_speed, has_sidecar, boost_amount;
    
    // Read inputs
    scanf("%d", &initial_speed);
    scanf("%d", &max_speed);
    scanf("%d", &has_sidecar);
    scanf("%d", &boost_amount);
    
    // TODO: Create a Motorcycle and initialize all its fields
    // Remember: Motorcycle embeds Vehicle as its first member
    
    // TODO: Demonstrate upcasting by calling boost
    // Cast your Motorcycle* to Vehicle* to pass it to the boost function
    
    // TODO: Print the motorcycle's information
    
    return 0;
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming