Functions
Functions
Declarations, overloads, parameters, return types, and callable objects.
Functions
Declarations, overloads, parameters, return types, and callable objects.
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.
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.
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
const& for large read-only objects.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.
auto area(double w, double h) -> double {
return w * h;
}
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.
constexprinline int triple(int x) { return x * 3; }
constexpr int square(int x) { return x * x; }
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;
});
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.
const& unless you intend to move from them.noexcept and contracts of intentvoid swap_buffers(Buffer& lhs, Buffer& rhs) noexcept {
lhs.swap(rhs);
}
noexcept when failure is not part of the contract.noexcept.Be conservative: a wrong noexcept can turn an exception into immediate termination.
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.
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.
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.
std::function only when you truly need type erasure and runtime polymorphism.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.
std::function.T by value for cheap scalar types and sink parametersconst T& for large read-only objectsT& only when mutation is part of the contractstd::span or std::string_view for borrowed sequence-style inputBefore 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.
std::optional<T> when the result may be absent without detailed failure informationstd::expected<T, E> when callers need structured local error handlingint square(int x) { return x * x; }
int main() {
for (int i = 0; i < 3; ++i) {
square(i);
}
}
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;
}