Question

C++ const Function Parameters: Effects and Function Signatures

cppparametersconstantsfunction-declarationconst-correctness

Question

Consider a mutator that receives one Boolean value by value:

void SetValue(const bool b) {
    my_val_ = b;
}

Does const on b have any practical effect? If so, what does it protect?

Also, why can a function declaration omit const on value parameters while its definition includes it?

// Header file
void func(int n, long l);

// Source file
void func(const int n, const long l) {
    // ...
}

Why do these declarations refer to the same function rather than different function signatures?

Short Answer

In C++, const on a parameter passed by value makes the function's local parameter variable read-only. It does not make the caller's value read-only, and it is not part of the function signature. You will learn the difference between top-level const on a value parameter and const that applies to an object accessed through a pointer or reference.

Concept

A value parameter is a new local object initialized from the argument supplied by the caller.

void SetValue(const bool b) {
    my_val_ = b;
}

Here, b is a local copy of the caller's Boolean value. The const means that the body of SetValue cannot assign a new value to that local copy:

void SetValue(const bool b) {
    // b = false; // Error: b is const
    my_val_ = b;
}

This can be useful as a local safeguard, but it does not change the caller's variable. A Boolean is already copied when passed by value, whether or not the parameter is declared const.

Why it is not part of the signature

For function parameter types, C++ ignores top-level cv-qualifiers (const and volatile) when determining a function's type. Therefore these declarations describe the same function:

void func(int n);
void func(const int n);

If they were distinct overloads, calling func(42) would have no meaningful way to select one: the caller passes an int value in either case. The const only affects whether the implementation may reassign its own local parameter.

This rule lets a header present a simple public declaration while the implementation chooses to protect its local copies.

When const does matter

const matters to a signature when it qualifies the object reached through a pointer or reference, rather than merely qualifying the local pointer/value variable itself:

void read(const int& value); // Cannot modify the referred-to int
void write(int& value);      // Can modify the referred-to int

void inspect(const int* ptr); // Cannot modify *ptr through ptr
void modify(int* ptr);        // Can modify *ptr through ptr

These pairs have different parameter types and can represent different overloads.

Mental Model

Think of a by-value parameter as a photocopy placed on the function's desk.

  • void f(int x) gives the function a photocopy it may write on.
  • void f(const int x) gives the function a photocopy with a “do not edit” label.

In both cases, the caller handed over the same kind of original value, and the function received a separate copy. That is why the label does not change the function's public calling interface.

A reference or pointer is more like an address to the caller's original object. In that case, const can decide whether the function is allowed to edit the original object through that address, so it is an important part of the parameter type.

Take Quiz

Syntax and Examples

A top-level const on a by-value parameter prevents reassignment of the local parameter.

void printTwice(const int count) {
    // count = 2; // Error: cannot assign to a const parameter

    for (int i = 0; i < count; ++i) {
        std::cout << "Hello\n";
    }
}

The caller may still change its own variable before or after the call:

int main() {
    int number = 3;
    printTwice(number);

    number = 10; // Fine: this is the caller's separate variable
}

For small value types such as bool, int, enums, and small structs, developers often omit parameter const because it does not communicate anything useful to callers:

void SetValue(bool b) {
    my_val_ = b;
}

For pointers, distinguish where const appears:

void example(const int* ptr); // The int is read-only through ptr
void example(int* const ptr); // The local pointer cannot point elsewhere

The first declaration has a different function parameter type from int*. The second has the same effective parameter type as int*, because the const applies only to the local pointer parameter.

Step by Step Execution

Trace this function:

void increaseCopy(const int number) {
    // number = number + 1; // Not allowed
    std::cout << number << '\n';
}

int main() {
    int score = 7;
    increaseCopy(score);
    std::cout << score << '\n';
}
  1. score is created in main with the value 7.
  2. increaseCopy(score) passes score by value.
  3. The function creates its own local parameter, number, initialized to 7.
  4. number is const, so the function cannot reassign it.
  5. The function prints 7.
  6. The function ends and its local parameter disappears.
  7. score was never modified, so main prints 7.

The output is:

7
7

Real World Use Cases

  • Configuration flags: A function may accept bool enabled or const bool enabled. Both receive a copy; const only prevents accidental reassignment inside the function.
  • Numeric calculations: A developer may use const double rate to ensure intermediate code does not overwrite the input parameter.
  • Implementation safety: In a long function, making a copied parameter const can prevent accidental reuse of its name for another value.
  • Read-only object inputs: For a large object that should not be copied or modified, use a const reference instead:
void render(const std::string& title);
  • Read-only buffers: For data passed through a pointer, use a pointer-to-const when the function should only read the data:
std::size_t countSpaces(const char* text);
Take Quiz

Real Codebase Usage

In most C++ codebases, public declarations normally omit top-level const on value parameters:

// Typical public API
bool isValidPort(int port);

A definition may optionally add it if the implementation benefits from preventing reassignment:

bool isValidPort(const int port) {
    return port >= 1 && port <= 65535;
}

However, many style guides discourage this for simple parameters because it adds visual noise without changing the API or protecting caller data.

For objects, real code more commonly uses const with references to express an API guarantee:

bool isValidUser(const User& user) {
    if (user.name.empty()) {
        return false; // Guard clause
    }

    return user.age >= 18;
}

This says two useful things:

  • user is not copied.
  • The function cannot modify user through this reference.

For mutable output parameters, use a non-const reference only when the function is intentionally expected to update the caller's object:

bool tryParsePort(const std::string& text, int& port);

Common Mistakes

Expecting const value parameters to protect the caller

void reset(const int value) {
    // value = 0; // Error
}

This does not make the caller's variable const. It only protects the function's copy. Use const int& when you need a read-only reference to an existing object, though using a reference for a small int is usually unnecessary.

Trying to overload only on top-level const

This is invalid because both declarations have the same signature:

void process(int value);
void process(const int value); // Error: redeclaration of the same function

Use meaningfully different parameter types if overloads are needed.

Confusing const int* with int* const

void read(const int* ptr);  // Cannot write to *ptr through ptr
void movePointer(int* const ptr); // Cannot reassign ptr inside the function

The first const affects the pointed-to object and matters to callers. The second affects only the local pointer variable and is ignored in the function signature.

Believing const makes pass-by-value faster

const does not avoid the copy. Passing by value still creates a parameter object. For expensive-to-copy objects that are only read, prefer const T&:

void logMessage(const std::string& message);

Adding const everywhere without considering readability

For scalar value parameters, const is optional implementation detail. Follow the project's style guide and reserve const for places where it clearly communicates mutability or ownership.

Comparisons

Parameter formCan the function reassign the parameter variable?Can it modify the caller's object through the parameter?Part of the function signature?
int valueYesNo; it is a copyYes, as int
const int valueNoNo; it is a copyNo difference from int
int& valueThe reference cannot be reseatedYesYes
const int& valueThe reference cannot be reseatedNoYes
int* ptrYesYes, if not nullYes
const int* ptrYesNo, through ptrYes
int* const ptrNoYes, if not nullNo difference from int*

For small, cheap-to-copy values, prefer value parameters:

void setEnabled(bool enabled);
void setCount(int count);

For larger objects that are read but not changed, prefer const references:

void displayProfile(const Profile& profile);
Take Quiz

Cheat Sheet

  • A parameter passed by value is a local copy.
  • const int x prevents assigning to local x inside the function.
  • const on a value parameter does not make the caller's variable const.
  • Top-level const is ignored when C++ determines a function parameter type.
void f(int x);
void f(const int x); // Same declaration
  • You cannot overload functions using only top-level parameter const.
  • const T& means “read the caller's object without copying it.”
  • const T* means “do not modify the pointed-to object through this pointer.”
  • T* const means “do not reassign this local pointer parameter.”
  • For simple scalar inputs, const on a value parameter is usually a style choice, not an API feature.

FAQ

Does const on a C++ value parameter have any effect?

Yes. It prevents the function body from assigning a new value to its local parameter variable. It does not affect the caller's argument.

Why is const int the same signature as int in a parameter?

C++ removes top-level const from parameter types when forming a function type. The qualifier affects only the local parameter object in the implementation.

Can I declare a function without const and define it with const parameters?

Yes, for value parameters. For example, void f(int); and void f(const int) {} match correctly.

Can I overload void f(int) and void f(const int)?

No. They are the same function signature, so the second declaration is a redeclaration rather than a new overload.

Should I use const on bool, int, or double parameters?

Usually it is optional. It can prevent accidental reassignment in the implementation, but many projects omit it for small value types to keep declarations concise.

When does parameter const affect overload resolution?

It matters when it applies to the referred-to or pointed-to object, such as int& versus const int&, or int* versus const int*.

Does const prevent a copy when an argument is passed by value?

No. const T value is still passed by value. Use const T& when avoiding a copy is appropriate.

Take Quiz

Mini Project

Description

Create a small settings class with functions that accept both scalar values and a text label. The project demonstrates that a const scalar value parameter protects only a local copy, while a const reference provides read-only access to a caller-owned object.

Goal

Implement settings updates and print a read-only label without modifying the caller's string.

Requirements

  • Create a Settings class with a Boolean enabled state.
  • Add a setter that accepts a Boolean by value.
  • Prevent reassignment of the setter's local Boolean parameter.
  • Add a method that accepts a const std::string& label and prints it.
  • Demonstrate that the original string remains usable after the call.
Take Quiz

Keep learning

Related questions

View more