A function gives an operation a name, parameter types, a return type, and a scope. A useful function does one coherent job and makes invalid or exceptional outcomes visible in its interface.
Pass small value types by value, read larger objects through const reference, use a non-const reference only for an intentional output or mutation, and use a pointer when absence is meaningful. Return values are usually clearer than output parameters.
Overload resolution uses the argument types and available conversions, not the return type. Defaults are substituted at the call site and should normally appear once in the public declaration.
#include <iostream>
#include <string>
std::string repeat(const std::string& text, int count = 2) {
std::string result;
for (int i = 0; i < count; ++i) result += text;
return result;
}
int main() {
std::cout << repeat("go") << '\n';
std::cout << repeat("ha", 3) << '\n';
}
gogo
hahaha
The inline specifier permits identical definitions in multiple translation units; compilers decide whether to inline calls. Define small header functions inline when linkage rules require it, not as a performance promise.
A function signature should reveal whether an argument is copied, observed, modified, optional, or consumed. Pass small scalar values by value. Pass large read-only objects by const reference. Use a non-const reference only when mutation is an intentional result, and use a pointer or optional-like type when absence is valid.
Overloads must represent meaningfully different call shapes. Avoid overload sets where integer conversions, default arguments, or braced initialization make the selected function surprising. Keep default arguments in one declaration and prefer a named options type when several Boolean or numeric defaults obscure the call.
#include <string>
#include <utility>
struct Profile {
std::string displayName;
};
std::string greeting(const Profile& profile) {
return "Hello, " + profile.displayName;
}
void rename(Profile& profile, std::string name) {
profile.displayName = std::move(name);
}
bool hasProfile(const Profile* profile) {
return profile != nullptr;
}
The signatures distinguish observation, mutation, value transfer, and optional access before reading the bodies.
Ambiguity occurs when two overloads require conversions of equal rank, so neither is a better match. Integer literals, implicit numeric conversions, and default arguments commonly trigger it.
Default arguments are substituted at the call site, so repeating or changing them across declarations can create errors or inconsistent behavior between translation units. Put the default on the public declaration and omit it from the definition.
No. <code>inline</code> primarily permits identical definitions in multiple translation units, which is why small header-defined functions use it.
Explore 500+ free tutorials across 20+ languages and frameworks.