Skip to content

C++ Values, References, Initialization, and const

C++ parameter and initialization syntax communicates lifetime and ownership choices. The goal is not to memorize every initialization form, but to recognize when an object is created, copied, moved, borrowed, or narrowed.

Initialization establishes an object

cpp
#include <iostream>
#include <string>

int main() {
    int count = 3;
    int limit{10};
    std::string name{"Ada"};
    auto doubled = count * 2;
    std::cout << name << ' ' << doubled << '/' << limit << '\n';
}

Brace initialization rejects many narrowing conversions:

cpp
// int count{3.5}; // ill-formed: narrowing

auto deduces a type from an initializer. It does not make C++ dynamically typed; the deduced type is fixed. Ordinary auto drops top-level references and const, so use auto& or const auto& when borrowing is intended.

References express aliases

cpp
void normalize(Record& record);             // mutable borrow
void print(const Record& record);            // read-only borrow
Record transformed(Record record);           // local value, returned by value
const Record* find_record(int id);            // optional pointer result

A reference must be initialized and normally cannot be reseated. It does not own the referred object and cannot outlive it. C++ has no runtime borrow checker; lifetime correctness remains the programmer's responsibility.

Pass small scalar values by value. Pass larger read-only objects by const& when copying is undesirable. Pass by value when the function needs its own copy and can benefit from move-in/copy-elision patterns. Use a mutable reference only when mutation is part of the function's visible contract.

const participates in interfaces

cpp
class Counter {
public:
    int value() const { return value_; }
    void increment() { ++value_; }
private:
    int value_{};
};

The trailing const says the member function does not modify the observable object through this, subject to mutable and indirect state. A pointer can itself be const, point to const, or both; read declarations from the name outward and use aliases when syntax obscures intent.

Constness is not universal deep immutability. A const object containing a pointer cannot reseat that member but may still designate mutable external state.

Named casts expose different claims

C++ splits the broad C-style cast syntax into operations with narrower meanings:

cpp
double average = static_cast<double>(total) / count;
auto *button = dynamic_cast<Button *>(widget);

static_cast performs checked-at-compile-time conversions such as numeric conversion, explicit construction, and class-hierarchy conversions that do not need a runtime type check. It can still narrow or lose information, so validating the source range remains the caller's job.

dynamic_cast checks a polymorphic class hierarchy at runtime. A failed pointer cast returns nullptr; a failed reference cast throws std::bad_cast. It is for navigation within an inheritance design, not arbitrary memory reinterpretation.

const_cast changes cv-qualification. Writing through the result is defined only when the original object was not actually const. reinterpret_cast requests a low-level reinterpretation whose useful operations depend on alignment, lifetime, aliasing, and ABI rules. Neither cast repairs an invalid object model.

A C-style cast can select among several of these behaviors, making review harder. Use the named form that states the intended operation and keep reinterpret_cast at narrow system boundaries.

Constant evaluation has several contracts

constexpr says a variable is a constant expression or that a function can run during constant evaluation when its arguments and body permit it:

cpp
constexpr int square(int value) noexcept {
    return value * value;
}

static_assert(square(6) == 36);
int runtime_square(int input) {
    return square(input); // ordinary runtime call is also allowed
}

C++20 consteval makes every potentially evaluated call occur at compile time. It suits operations that have no meaningful runtime form, such as validating a compile-time format or generating a fixed lookup table. constinit applies to static or thread storage and requires static initialization; it does not make the object const:

cpp
constinit int process_count = 0; // initialized before dynamic startup work

These keywords solve different problems. const restricts mutation through an interface, constexpr enables constant evaluation, consteval requires it, and constinit controls initialization timing.

Moves permit resource transfer

An lvalue has persistent identity; an rvalue can often be treated as expiring. std::move is a cast that permits move operations—it does not move by itself.

cpp
std::string source = "report";
std::string destination = std::move(source);

After a move, a standard-library object remains valid but its value is generally unspecified unless its contract says more. It can be destroyed or assigned; do not assume it is empty.

Return values directly. Copy elision and moves make return result; efficient, while return std::move(result); can inhibit elision.

Views require lifetime discipline

std::string_view and std::span are non-owning views. They make pointer-plus-length interfaces convenient but do not extend storage lifetime:

cpp
std::string_view bad_name() {
    std::string local = "temporary";
    return local; // dangling view
}

Use views for bounded borrows whose owner clearly outlives the use. Use owning values when data must escape.

Optional prompts

Explain: What does std::move guarantee?

Answer: It changes value category so an overload may move. The selected operation decides what happens; afterward, moved-from objects must be used according to their documented valid-but-unspecified state.

Debug: A string_view returned from a function displays garbage. What lifetime should be inspected?

Answer: The storage it views. A view does not own or extend the lifetime of a local string or temporary.

Explain: Why prefer static_cast<int>(value) to (int)value in C++?

Answer: The named cast limits and communicates the requested conversion category. A C-style cast can silently perform several stronger operations, including casting away qualifiers or reinterpreting representation.

Further reference