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: 50This 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
EasyLet'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 aVehiclestruct with two fields:speed(an integer for current speed) andmax_speed(an integer for the vehicle's top speed). Then define aMotorcyclestruct that embedsVehicleas its first member and adds ahas_sidecarfield (an integer, where 1 means yes and 0 means no). Declare two functions:boostthat takes aVehicle*and an amount to increase speed, andprint_motorcyclethat takes aMotorcycle*.vehicle.c: Implement both functions. Yourboostfunction should increase the vehicle's speed by the given amount, but cap it atmax_speed—if the new speed would exceed the maximum, set it tomax_speedinstead. Yourprint_motorcyclefunction should display all the motorcycle's information including its embedded vehicle data.main.c: Create aMotorcycleand initialize all its fields. Then demonstrate upcasting by calling theboostfunction—cast yourMotorcycle*to aVehicle*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: YesWhere 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: 50This 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;
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Modular Programming Basics
Header FilesInclude GuardsSource FilesStatic FunctionsRecap: Modular Calculator4Encapsulation
Opaque Pointers ConceptDefining Opaque StructsGetters and SettersValidation in SettersRecap: Secret Box2Objects and Methods
Structs as ObjectsThe 'Self' PointerConst CorrectnessPointer vs ValueHelper MethodsRecap: Point Manager5Project: Simple Bank Account
Project SetupImplementation of Account3Object Lifecycle
Constructor PatternDestructor PatternStack InitializationDeep CopyRecap: String Wrapper6Inheritance via Composition
Struct EmbeddingThe First Member RuleAccessing Parent MembersUpcastingRecap: Shape Hierarchy