Parameters are the inputs that allow one function to handle many cases. Without them, every variation of a task would need its own function: one to add a wireless mouse to a cart, another for a USB cable, another for a notebook. With parameters, a single addToCart function works for any product. This lesson covers what parameters are, the difference between the names in the function signature and the values at the call site, type matching, naming conventions, and how const on a parameter keeps a function from editing its own copy of an input.
A parameter is a named placeholder inside a function definition. When the function is called, the caller supplies a value, and that value flows in through the parameter. The function uses the parameter like a regular local variable for the duration of the call.
A function that prints a welcome line for a customer would, without parameters, only ever greet one name:
The same function handles three different greetings. customerName is the parameter. Each call passes a different string in, and the function does its job with whatever value it received.
A function with parameters is a template for behavior. The body says "do this thing with these inputs," and the inputs change every time the function runs. That single idea is what makes functions reusable instead of one-off scripts.
These two terms get used loosely, but they refer to two different things, and the distinction shows up in compiler error messages all the time.
In the code below, productId and quantity are formal parameters. 42 and 3 are the actual arguments.
The compiler matches arguments to parameters strictly by position: the first argument becomes the value of the first parameter, the second argument becomes the second parameter, and so on. There's no name-based matching at the call site in C++. With addToCart(42, 3), the function has no way to know whether 42 was meant for productId or quantity; it just takes what's there in order.
The diagram shows what happens when addToCart(42, 3) runs. The two arguments flow into the two parameters by position, and then the body executes with productId = 42 and quantity = 3.
A small note on terminology: in casual conversation, "parameter" and "argument" get used almost interchangeably, and even some books do this. The C++ standard is precise about it, and so are compiler error messages, so keep the distinction in mind. When the compiler says "too many arguments to function addToCart," it means more values were passed at the call site than the function declares parameters.
A function can declare any number of parameters, including zero. The parameter list lives between the parentheses in the function header, and parameters are separated by commas. Each parameter needs a type and a name.
A function with no parameters takes no input. Its job is fixed and doesn't vary call to call.
The empty parentheses are required even though there are no parameters. void printHeader(void) (a C-style holdover) is also valid, but plain () is the modern convention.
A function with one parameter takes a single input.
A function with multiple parameters takes several inputs. Each one is declared independently with its own type and name.
Three parameters, three arguments per call, matched by position. Each parameter has its own type: std::string, int, double. C++ doesn't require parameters to share a type, and mixing types is the normal case.
There's no hard limit on parameter count, but past four or five parameters a function gets hard to call correctly. The order matters, and a long list invites swapping arguments by accident. When the parameter count grows past six or seven, the usual fix is to group related parameters into a struct.
Parameter names live inside the function body, so they don't have to match anything outside. The names are for the human reading the function, not for the compiler. Good names make the function self-documenting, and bad names make it a guessing game.
Two principles to follow:
quantity over qty or q or intVar. The type is already in the declaration.camelCase for parameters (customerName, productId, unitPrice). Some use snake_case (customer_name). Pick one and stick with it.Two versions of the same function:
Both compile. Both produce the same output. The first one forces a reader to step into the body, figure out what a, b, and c mean, and remember the order. The second one tells you everything you need to know from the signature alone.
Function declarations (the ones in header files, before the definition) are allowed to omit parameter names entirely:
The compiler accepts this because the types are enough to match calls. Don't write declarations this way. The name is the only documentation a caller gets when reading a header, and stripping it out forces them to dig into the implementation.
The compiler checks that every argument is compatible with the type of the parameter it's being matched to. If the types match exactly, the value is copied straight in. If they don't match but the compiler knows how to convert one to the other, it applies an implicit conversion before the call.
A clean exact match:
When types differ but a conversion is well-defined, the compiler inserts it automatically. Numeric conversions are the most common case: int to double, double to int, char to int, and so on.
The argument is an int, the parameter is a double, and the compiler promotes 25 to 25.0 before the call. This direction (int to double) is safe because every representable int fits in a double without loss for typical values.
The other direction is risky. Going from double to int is a narrowing conversion, and it drops the fractional part without warning.
The compiler accepts the call (often with a warning under -Wall or -Wconversion), but the value 2.7 becomes 2 before it reaches the body. The caller probably meant something different. This is the kind of bug that survives code review because the call site looks reasonable.
Implicit numeric conversions are essentially free at runtime; the cost is correctness, not performance. A double to int narrowing or an unsigned-to-signed mismatch can change values without warning unless the build uses -Wall -Wconversion.
The narrowing problem also shows up between integer types of different widths:
On a typical 64-bit system, long is 64 bits and int is 32 bits, so 5000000000 won't fit in an int. The value gets truncated, and the printed result is some nonsense like 705032704. The compiler usually warns, but the program still runs and produces a wrong answer.
Two habits cut down on conversion bugs:
int (or std::size_t for counts that could exceed INT_MAX). A function that represents money takes double (or a fixed-point type in real code).-Wall -Wconversion) and treat them as errors during development.Because C++ matches arguments to parameters strictly by position, the order of the parameter list is part of the function's contract. Swapping two arguments at the call site is a bug the compiler can rarely catch.
Both calls compile because both arguments are double. The first one applies a 10% discount to an $80 item ($72). The second one accidentally applies an 80% discount to a $10 item ($2). The function did exactly what it was told. The bug is at the call site, but the parameter order made it easy to write.
A few patterns help here:
applyDiscount(price, percent), the price is the thing being discounted, so it leads.setShippingAddress(name, street, city, zip) reads left to right.void copy(int* src, int* dst) is asking for trouble.When two adjacent parameters share a type and the order isn't obvious, named-argument workarounds help readability.
Storing the values in well-named local variables before the call gives the reader a label even if the parameter list itself is ambiguous. This isn't a language feature, just a discipline that prevents most order-related mistakes.
The signature uses const std::string& for the currency code, which is a pass-by-reference parameter. For the rest of this lesson, focus on the parameter list rather than the reference syntax.
Every parameter in this lesson takes the argument by value, meaning the function gets its own copy of the data. Writing to the parameter inside the body changes only that local copy, not the caller's variable.
cartCount is still 5 after the call. The function received a copy of cartCount, added one to the copy, and discarded it when the function returned. The caller's variable was never touched.
That's the default model for C++ parameters, and it's the only one this lesson uses. Pass by reference and pass by pointer allow a function to read or modify the caller's original variable, but they have their own syntax (int& or int*) and their own trade-offs.
For now, the takeaway is simple: a function with a plain parameter type like int or double or std::string works on a copy. The caller's variable is safe from being clobbered.
Passing built-in types like int and double by value is as cheap as a register copy. Passing a std::string or a std::vector by value is not free, because the entire container's contents get copied.
const on a ParameterA subtle thing about pass-by-value: even though writing to the parameter doesn't affect the caller, the local copy can still be modified. Sometimes that's useful (using the parameter as a working variable), but more often it's a source of confusion. A reader looking at a long function might think a parameter still holds its original value when, halfway through, it doesn't.
Adding const in front of a value parameter prevents the function body from writing to it. The parameter becomes read-only inside the function.
Trying to write productId = 99 inside the body would fail with error: assignment of read-only parameter 'productId'. The function is now contractually bound (to its own author) not to modify either parameter.
This const applies only inside the function body. It doesn't change the caller's experience. Both of these declarations behave identically from the outside:
Some C++ style guides recommend marking every value parameter const to make the function's intent explicit. Others say it's clutter for built-in types and that the convention should be reserved for cases where the body is long enough that re-assignment would be confusing. Both positions are defensible; pick the team's convention.
Where const on a value parameter helps most is in functions that take large structs or strings and want to signal "I'm only reading this." Even there, modern C++ usually pairs const with a reference (const std::string&) to avoid the copy. The plain const T form (without the &) is the one this lesson is about.
This version of logPurchase still copies the string and the double (it's pass-by-value), but the const keeps the function body honest. The reader knows, from the signature alone, that nothing inside is going to re-assign customerName partway through.
A small program puts several of the ideas above together. The function applyDiscount takes a price and a percent, and returns the discounted amount. formatPrice takes a value and a currency code and prints it. Both functions use clear parameter names, const on inputs they don't intend to modify, and pass-by-value for everything.
A few things to note in this program. The parameter names in applyDiscount (price, percent) tell the reader what each input represents, so the caller knows the first argument is the thing being discounted and the second is the rate. The const on each parameter signals that the function won't mutate its local copies. And the call site passes the cart total first and the discount second, matching the declaration order.
Swapping the arguments at the call site (applyDiscount(discountPercent, cartTotal)) would have the function compute 15.0 * (1.0 - 129.95 / 100.0), which produces a wildly wrong negative number. Type matching alone isn't enough; argument order has to be right too.
A short list of the parameter-related mistakes that show up most often in code, and how to spot them.
| Mistake | Symptom | Fix |
|---|---|---|
| Mixing up argument order | Function returns wrong values for "obvious" inputs | Use clearly-named local variables for each argument; double-check the signature |
Passing a double to an int parameter | Fractional part dropped without warning | Match types exactly, or convert explicitly with static_cast so the intent is visible |
| Writing to a parameter expecting the caller to see the change | "Why didn't my variable update?" | Pass-by-value never modifies the caller; use pass-by-reference when modification is the goal |
| Naming parameters with single letters | Reader has to read the whole body to understand the function | Use meaningful names like productId, quantity, price |
| Long parameter lists (six or more) | Easy to swap arguments by accident | Group related parameters into a struct (covered in the Structs section) |
Every one of these stems from treating the parameter list as a detail rather than as the function's public contract. Parameters are the first thing a caller reads, and often the only documentation they get.
9 quizzes