Functions

Functions

Declarations, overloads, parameters, return types, and callable objects.

Functions

Declaration and definition

int add(int lhs, int rhs);

int add(int lhs, int rhs) {
    return lhs + rhs;
}

Declare first when you need a stable interface in a header or want to separate interface from implementation. Define directly in-place when the function is tiny and local.

Default arguments

void log(std::string_view message, int level = 1);

Put default arguments in one place, usually the declaration, so callers do not have to guess which default is authoritative.

Pass by value vs reference

void set_name(std::string& out, std::string_view value);
void print(const std::string& name);
void store(std::string name); // copy or move in
class User {
public:
    void set_name(std::string name) {
        name_ = std::move(name);
    }

private:
    std::string name_;
};

This sink-style setter is often cleaner than maintaining separate lvalue and rvalue overloads.

Return types

auto area(double w, double h) -> double {
    return w * h;
}

Overloading

void draw(int x, int y);
void draw(double x, double y);

Only overload when the operations mean the same thing. If one overload parses text and another renders graphics, they should not share a name just because the parameter lists differ.

Inline and constexpr

inline int triple(int x) { return x * 3; }
constexpr int square(int x) { return x * x; }

Lambdas as function objects

auto is_even = [](int x) { return x % 2 == 0; };

Lambdas are ideal for short local policies passed to algorithms.

auto active = std::ranges::count_if(users, [](const User& user) {
    return user.active;
});

Variadic templates

template <typename... Args>
void print_all(const Args&... args);

Use them when the operation is truly uniform across an arbitrary number of arguments. If the logic gets complicated, a container or tuple-based design may be easier to maintain.

Best-practice reminders

noexcept and contracts of intent

void swap_buffers(Buffer& lhs, Buffer& rhs) noexcept {
    lhs.swap(rhs);
}

Be conservative: a wrong noexcept can turn an exception into immediate termination.

Forwarding references

template <typename T>
void push_name(std::vector<std::string>& names, T&& name) {
    names.emplace_back(std::forward<T>(name));
}

Use forwarding references in generic wrappers that should preserve lvalue/rvalue behavior.

Avoid using them in ordinary non-template APIs. Most of the time a plain value, reference, or string_view parameter is simpler.

Practical forwarding pattern

template <typename Factory, typename... Args>
auto make_with(Factory&& factory, Args&&... args) {
    return std::forward<Factory>(factory)(std::forward<Args>(args)...);
}

This is the kind of wrapper where perfect forwarding pays off: the wrapper should preserve the caller's argument category instead of forcing extra copies.

Container helper pattern

template <typename T>
void append(std::vector<std::string>& values, T&& value) {
    values.emplace_back(std::forward<T>(value));
}

If the caller passes an lvalue, it is copied. If the caller passes an rvalue, it can be moved. That is the practical value of forwarding references.

Callable design choices

std::function<bool(int)> predicate = [](int value) {
    return value % 2 == 0;
};

std::function is convenient, but it can allocate and erase type information. Use it because you need that flexibility, not by default.

Quick selection guide

Parameter design guide

Overload sanity check

Before adding an overload, ask whether the overloads really represent the same action. If they differ in side effects, domain meaning, or expected failure behavior, they usually deserve distinct names instead of one overloaded family.

Return-type guide

Example in practice

int square(int x) { return x * x; }

int main() {
    for (int i = 0; i < 3; ++i) {
        square(i);
    }
}

Try this variation

Refactor the example into a named helper with a single responsibility. If the logic gets easier to test, the design probably improved.

bool is_even(int value) {
    return value % 2 == 0;
}

int main() {
    return is_even(4) ? 0 : 1;
}