Functions
Declarations, overloading, default arguments, inline, lambdas, and callable design.
32 questions
JuniorTheoryVery commonPassing arguments by value, reference, and pointer — when to use each?
Passing arguments by value, reference, and pointer — when to use each?
By value: small trivially copyable or when a private copy is needed. By const T&: default for non-pointer types not modified. By T&: modifying caller. By T*: when null is meaningful.
Common mistakes
- ✗Passing large objects by value in performance-sensitive code — causes an unnecessary copy
- ✗Using a non-const reference when only reading the value — misleads callers into thinking the object may be modified
- ✗Dereferencing a pointer parameter without a null check when the pointer may legally be null
Follow-up questions
- →What is the 'sink parameter' pattern and when does passing by value +
std::moveoutperform overloading lvalue/rvalue refs? - →How does passing by
T&&(rvalue reference) differ semantically from passing byT?
JuniorDebuggingVery commonWhat happens if you return a reference to a local object?
What happens if you return a reference to a local object?
The local object is destroyed when the function returns. The returned reference is immediately dangling — any dereference is UB. Compilers warn with -Wall.
Common mistakes
- ✗Thinking the reference 'might' work because the stack memory wasn't yet reused — it is still UB
- ✗Returning
const std::string&from a getter that builds the string locally — the temporary is destroyed - ✗Not noticing the compiler warning — it is almost always correct about dangling references
Follow-up questions
- →How does RVO/NRVO eliminate the cost of returning by value for large objects?
- →Is returning a
staticlocal reference safe in a multithreaded program?
JuniorTheoryVery commonWhat is a lambda? How do you capture outer variables?
What is a lambda? How do you capture outer variables?
Anonymous function object created inline. Captures: [=] value, [&] ref, [x]/[&x], [y = expr] init, [*this] copy. Captureless converts to a function pointer.
Common mistakes
- ✗Using
[&]capture in a lambda stored beyond the captured variables' lifetimes — dangling references - ✗Forgetting that
[=]capturesthisby pointer in a member function — use[*this](C++17) for a safe copy - ✗Expecting two identical lambda expressions to have the same type — each lambda has its own unique type
Follow-up questions
- →What is a generic lambda (C++14) and how does it relate to function templates?
- →When would you use a
mutablelambda?
JuniorTheoryVery commonWhat is function overloading and how does overload resolution work?
What is function overloading and how does overload resolution work?
Multiple functions share a name but differ in parameter types or count. The compiler picks the best match: exact type, promotions, standard conversions, user-defined conversions.
Common mistakes
- ✗Trying to overload on return type only — not allowed in C++
- ✗Creating ambiguous overloads that force the compiler to emit an error
- ✗Forgetting that default arguments can create ambiguity with existing overloads
Follow-up questions
- →How does name mangling relate to overloading?
- →What happens when no overload matches exactly?
MiddleDebuggingVery commonWhy does this returned lambda read freed memory?
Why does this returned lambda read freed memory?
The lambda captures count by reference ([&]), but count is destroyed when makeCounter returns, so calling the returned std::function reads a dangling reference — UB. Fix: capture by value, [count]() mutable { return ++count; }.
Common mistakes
- ✗Believing reference capture extends the captured variable's lifetime
- ✗Blaming std::function instead of the by-reference capture
- ✗Thinking adding mutable fixes what is actually a lifetime bug
Follow-up questions
- →When is capturing by reference in a lambda actually safe?
- →Why does a by-value capture need
mutableto be modified?
JuniorTheoryCommonWhat are the rules for default arguments and what pitfalls do they introduce?
What are the rules for default arguments and what pitfalls do they introduce?
Default arguments must be listed right-to-left (trailing first). Resolved at the call site using the declaration visible there, not the definition.
Common mistakes
- ✗Redefining a default argument in a different translation unit — undefined behaviour
- ✗Combining default arguments with overloads to create ambiguous calls
- ✗Expecting virtual overrides to inherit default arguments from the base
Follow-up questions
- →Why do virtual functions and default arguments interact poorly?
- →What happens if two translation units see different default arguments for the same function?
JuniorTheoryCommonWhat is the order of evaluation of function arguments in C++?
What is the order of evaluation of function arguments in C++?
Argument evaluation order is unspecified — f(a++, a++) is UB. C++17 sequences the callee before any argument, but argument-to-argument order remains unspecified.
Common mistakes
- ✗Assuming left-to-right evaluation — common on x86 calling conventions but not guaranteed by the standard
- ✗Modifying a variable in one argument and reading it in another — classic UB example
- ✗Thinking C++17 fixes all ordering issues — it only fixes callee-before-args, not arg-to-arg
Follow-up questions
- →What sequencing rules did C++17 introduce that C++14 did not have?
- →How do you rewrite
f(i++, i++)to make the intent clear and the behaviour defined?
JuniorTheoryCommonWhat is a function pointer and how do you declare one?
What is a function pointer and how do you declare one?
Holds the address of a function with a specific signature. Declaration: int (*fp)(double, double);. Call: fp(a, b). Use using BinaryOp = int(*)(double, double); for readability.
Common mistakes
- ✗Confusing pointer-to-function with pointer-to-member-function — member function pointers have a different, incompatible type
- ✗Not using a
typedef/usingfor the function pointer type — raw declaration syntax is notoriously hard to read - ✗Storing a capturing lambda in a raw function pointer — only non-capturing lambdas are implicitly convertible
Follow-up questions
- →What is the difference between
std::functionand a raw function pointer in terms of overhead? - →How do you store a pointer to a non-static member function and call it later?
JuniorTheoryCommonWhat is short-circuit (lazy) evaluation in C++?
What is short-circuit (lazy) evaluation in C++?
&& and || evaluate the left operand first; if the result is determined, the right is skipped. Standard-guaranteed and sequenced. The ternary ? : also short-circuits. Overloaded &&/|| do NOT.
Common mistakes
- ✗Relying on short-circuit evaluation with overloaded
&&/||— overloaded operators always evaluate both sides - ✗Placing a side-effect-heavy expression on the right, expecting it to always run — it won't if the left determines the result
- ✗Not knowing that the comma operator also sequences left-before-right, but
|and&do not short-circuit
Follow-up questions
- →How do C++23
std::logical_andandstd::logical_ordiffer from&&and||? - →When is short-circuit evaluation useful for guarding null pointer checks?
JuniorTheoryCommonWhat is a function stack frame and what can cause stack overflow?
What is a function stack frame and what can cause stack overflow?
Per-call area for local variables, saved registers, return address, spilled arguments. Stack overflow happens when nested calls or large locals exceed the stack limit.
Common mistakes
- ✗Assuming the stack grows without practical limits
- ✗Allocating multi-megabyte arrays as local variables
- ✗Confusing the call stack with the C++ container std::stack
Follow-up questions
- →Where are function instructions stored?
- →How would you avoid stack overflow in recursive code?
MiddleTheoryCommonWhat is argument-dependent lookup (ADL), and when does it surprise you?
What is argument-dependent lookup (ADL), and when does it surprise you?
ADL adds the namespaces of a call's argument types to the set searched for an unqualified function name. It lets swap(a, b) or operator<< find the right overload without a qualifier. It surprises you by silently pulling in an unintended overload from an argument's namespace.
Common mistakes
- ✗Writing
std::swap(a, b)instead of theusing std::swap; swap(a, b)idiom that letsADLfind a customswap - ✗Assuming a qualified call
ns::f(x)still doesADL— qualification disables it entirely - ✗Defining a free function in
namespace stdto makeADLfind it, instead of placing it next to the type
Follow-up questions
- →Why is the
using std::swap; swap(a, b)two-step idiom recommended? - →How do hidden friends interact with ADL and ordinary lookup?
MiddleTheoryCommonHow does C++14 return-type deduction work and when does it fail?
How does C++14 return-type deduction work and when does it fail?
auto f() { return expr; } deduces the return type by the same rules as auto x = expr. All returns must yield the same type. Recursion only after the first return.
Common mistakes
- ✗Two
returnstatements yielding different types — compile error - ✗Recursive call before any
return— compiler can't deduce yet - ✗Forward-declaring
auto f();and defining elsewhere — caller can't see deduced type
Follow-up questions
- →Why does
decltype(auto)matter for forwarding return types perfectly? - →How does the lambda's
autoreturn differ from a function's?
MiddleTheoryCommonWhat does constexpr on a function mean and how has it evolved through C++14/17/20?
What does constexpr on a function mean and how has it evolved through C++14/17/20?
Permits compile-time evaluation with constant args; otherwise runs at runtime. C++11 — single return. C++14 — loops, locals. C++20 — virtual, allocations, std::vector/std::string.
Common mistakes
- ✗Marking everything
constexprregardless of need — no harm but adds API surface - ✗Calling a non-constexpr function (e.g. legacy library) inside a constexpr function — silently turns it into a runtime call
- ✗Confusing
constexprwithconst—constexprimplies const for objects, but for functions it's about evaluatability
Follow-up questions
- →What's the difference between
constexprandconsteval? - →What is
std::is_constant_evaluated()and when do you use it?
MiddleDebuggingCommonWhat are the pitfalls of default function arguments in virtual functions and across translation units?
What are the pitfalls of default function arguments in virtual functions and across translation units?
Defaults bind statically by the static type of the pointer/reference. Base* b = new Derived; b->f(); uses Base's default though Derived::f runs — the override's = 2 is dead.
Common mistakes
- ✗Overriding a virtual with a different default value — call uses base's default
- ✗Putting different defaults in two headers — ODR violation
- ✗Defaults that depend on globals — non-obvious behaviour at call site
Follow-up questions
- →Why does the standard pick static binding for default arguments?
- →How does
std::optional/ sentinel value replace default arguments cleanly?
MiddleTheoryCommonWhat is a functor? Write an example.
What is a functor? Write an example.
A functor (function object) is any class that overloads operator(). Unlike a function pointer, it carries state in members and inlines — no type-erasure overhead.
Common mistakes
- ✗Forgetting to mark
operator()asconstwhen the functor does not mutate state — prevents use withconstalgorithms - ✗Storing expensive-to-copy state in a functor passed by value to an algorithm — STL may copy the functor internally
- ✗Using
std::functionwhen a template parameter accepts the callable —std::functionadds type-erasure overhead unnecessarily
Follow-up questions
- →What is the difference between a stateful functor and a lambda with captures?
- →How does
std::bindrelate to functors and why is it mostly superseded by lambdas?
MiddleTheoryCommonWhat does the inline keyword actually guarantee in modern C++?
What does the inline keyword actually guarantee in modern C++?
inline suppresses ODR so the same definition can appear in multiple TUs — useful for header functions. The expansion hint is mostly ignored by modern compilers.
Common mistakes
- ✗Assuming inline always eliminates the call overhead
- ✗Thinking inline gives the function internal linkage
- ✗Placing non-inline definitions in headers and expecting no linker errors
Follow-up questions
- →What is the difference between inline and __forceinline / __attribute__((always_inline))?
- →When would you use NOINLINE?
MiddleTheoryCommonWhat does mutable mean for lambdas and what are the risks of capturing this?
What does mutable mean for lambdas and what are the risks of capturing this?
By default a lambda's operator() is const, so value captures can't be modified. mutable removes that const. Capturing this stores a pointer — dangles if the object dies first.
Common mistakes
- ✗Thinking mutable changes the original captured-by-value variable
- ✗Capturing this into async callbacks without controlling object lifetime
- ✗Using [&] in callbacks that outlive the current scope
Follow-up questions
- →How can shared_ptr or weak_ptr help with async callbacks?
- →What is the difference between [this] and [*this]?
MiddleTheoryCommonWhat is C++ name mangling and how does it interact with extern "C"?
What is C++ name mangling and how does it interact with extern "C"?
Mangling encodes signatures (parameters, namespaces, templates) into the symbol name so the linker distinguishes overloads. Each ABI has its own scheme. extern "C" disables mangling for C interop.
Common mistakes
- ✗Forgetting
extern "C"on a function called from C — link error (undefined reference) - ✗Trying to overload an
extern "C"function — there's no mangling, so you can't - ✗Mismatch between ABI versions when linking object files compiled with different compilers
Follow-up questions
- →How do you read a mangled name (
c++filt)? - →Why is the Itanium ABI used on Linux despite the name?
MiddlePerformanceCommonWhat is the runtime cost of a virtual function call and when does it matter?
What is the runtime cost of a virtual function call and when does it matter?
Two indirections: load vptr, load function pointer from vtable, jump. Cost is a few ns plus a possible cache miss; more importantly the call cannot be inlined.
Common mistakes
- ✗Profiling without
-O2and concluding virtual is too slow - ✗Adding
virtualto leaf-only methods that could befinal(loses devirt opportunity) - ✗Choosing CRTP for shallow polymorphism with one or two types — overkill
Follow-up questions
- →How does the compiler perform devirtualisation?
- →Why does
finalhelp even on a class (not just on a method)?
SeniorTheoryCommonWhat is perfect forwarding and how do you implement it?
What is perfect forwarding and how do you implement it?
Preserves value category (lvalue/rvalue) and cv-qualification when forwarding args. Pattern: template<class... A> void wrap(A&&... a) { f(std::forward<A>(a)...); }. Without forward, rvalues become lvalues.
Common mistakes
- ✗Using
std::move(args)...instead ofstd::forward<Args>(args)...— wrongly moves lvalues - ✗Confusing
T&&in non-template context with a forwarding reference — it's just rvalue ref there - ✗Overloading on forwarding refs vs concrete types — forwarding ref usually wins, hijacking calls
Follow-up questions
- →What is reference collapsing?
- →How does C++20
std::forward_likediffer fromstd::forward?
MiddleTheoryOccasionalWhat is a function contract (preconditions, postconditions, invariants)?
What is a function contract (preconditions, postconditions, invariants)?
Obligations between caller and callee. Preconditions — caller must satisfy. Postconditions — callee guarantees. Invariants — properties holding after every public op.
Common mistakes
- ✗Silently handling violated preconditions instead of asserting — hides caller bugs and leads to harder-to-debug failures downstream
- ✗Not documenting the contract — callers cannot know what is valid without it
- ✗Checking preconditions in release builds when they are expensive — use
assertwhich compiles out in NDEBUG
Follow-up questions
- →How do
assert,[[expects]], and exception throwing differ as precondition enforcement mechanisms? - →What is Design by Contract (DbC) and which languages support it natively?
MiddleTheoryOccasionalWhat is a generic lambda, and how does it relate to a function template?
What is a generic lambda, and how does it relate to a function template?
A generic lambda (C++14) uses auto for a parameter, making its operator() a member template. Each distinct argument type instantiates a separate operator(), exactly like a function template. The closure is one type; only the call operator is templated and adapts per call site.
Common mistakes
- ✗Thinking each
auto-parameter call produces a new closure type — there is one closure type with a templatedoperator() - ✗Expecting two argument types to share one
operator()body — each type instantiates its own, with its own errors - ✗Forgetting
decltype(auto)orauto&&for parameters that must perfectly forward inside the body
Follow-up questions
- →How do you add an explicit template parameter list to a lambda (C++20)?
- →When would a generic lambda be preferable to a named function template?
MiddleTheoryOccasionalWhat is an init-capture, and why is it needed to capture a variable by move?
What is an init-capture, and why is it needed to capture a variable by move?
An init-capture (C++14) introduces a new closure member with an initializer: [p = std::move(ptr)]. Plain [ptr] only copies; the capture syntax cannot express a move. Init-capture lets you initialize the member from any expression — moving a unique_ptr in, or computing a fresh value.
Common mistakes
- ✗Trying to capture a
unique_ptrwith plain[ptr]and hitting a deleted-copy-constructor error - ✗Forgetting that the init-capture member is initialized once, when the lambda is created, not per call
- ✗Naming the init-capture member the same as the source variable and confusing which one the body uses
Follow-up questions
- →How do you move a member into a lambda inside a member function?
- →Can an init-capture use a pack expansion in C++20?
MiddleCodeOccasionalImplement a minimal signal/slot mechanism using lambdas and std::function
Implement a minimal signal/slot mechanism using lambdas and std::function
Store callbacks as std::vector<std::function<void()>> keyed by token, and iterate on emit(). Lambdas with captures fit naturally — std::function type-erases the closure.
Common mistakes
- ✗Capturing local variables by reference when the lambda outlives the scope
- ✗Copying
std::functionobjects that wrap large captures — preferstd::move - ✗Forgetting that
std::functionhas ~10-20% overhead vs a direct call due to heap allocation and type erasure
Follow-up questions
- →How would you make the slot connection thread-safe?
- →What is
std::move_only_functionand when would you choose it overstd::function?
MiddleTheoryOccasionalWhen do you need std::mem_fn and when has it been replaced by lambdas?
When do you need std::mem_fn and when has it been replaced by lambdas?
std::mem_fn(&Class::method) adapts a member-function pointer into a callable taking the object first. Useful with STL algorithms; modern code prefers a lambda.
Common mistakes
- ✗Using
std::bindfor what a lambda expresses better — bind composition is hard to read - ✗Forgetting that
mem_fncan take pointer or reference and dispatches accordingly - ✗Storing
mem_fn's result in a typed variable — auto is the practical choice
Follow-up questions
- →Why is
std::bindlargely deprecated by lambdas? - →How would you bind only some arguments while leaving others free?
MiddleTheoryOccasionalWhat is a trailing return type and when is it required?
What is a trailing return type and when is it required?
auto f(args) -> ReturnType puts the return type after the parameter list. Required when the return type depends on params: auto add(T a, U b) -> decltype(a + b). Still needed for SFINAE-friendly templates.
Common mistakes
- ✗Using just
autofor templates participating in overload resolution — caller can't SFINAE on the return type - ✗Forgetting that
auto f() -> intis identical toint f()for non-templates - ✗Mixing
decltype(expr)with reference-collapsing surprises in trailing return
Follow-up questions
- →How does
decltype(auto)differ fromautoin return type? - →When does C++14
autodeduction fail and require-> T?
MiddleTheoryOccasionalHow do C-style variadic functions differ from C++11 variadic templates?
How do C-style variadic functions differ from C++11 variadic templates?
C-style ... with va_list is type-unsafe — callee guesses types via format string or sentinel; mismatches cause UB. C++11 variadic templates template<class... A> are type-safe — each arg keeps its type.
Common mistakes
- ✗Mixing
va_listand a variadic template in the same API - ✗Forgetting fold expressions:
(std::cout << ... << args)is much cleaner than recursive base+general overloads - ✗Passing non-trivially-copyable types through
va_list— UB
Follow-up questions
- →What are unary vs binary fold expressions?
- →How does
std::format(C++20) replace printf-style varargs safely?
SeniorTheoryOccasionalHow does noexcept(expr) enable conditional noexcept on templates?
How does noexcept(expr) enable conditional noexcept on templates?
noexcept(expr) evaluates expr at compile time and propagates the bool into the noexcept spec. Pattern: swap(T&, T&) noexcept(noexcept(swap(a.m, b.m))).
Common mistakes
- ✗Writing
noexcept(true)on a function that calls something potentially throwing — terminate at runtime - ✗Forgetting the inner
noexcept(...)and writingnoexcept(swap(a, b))(which is the operand!) — common typo - ✗Marking move ctor
noexcepteven when a member's move can throw — UB by terminate
Follow-up questions
- →Why does the standard library require
noexceptmove for the strong guarantee in vector? - →How does
is_nothrow_move_constructible_vget evaluated?
SeniorPerformanceOccasionalWhen does the compiler perform tail-call optimisation in C++?
When does the compiler perform tail-call optimisation in C++?
The C++ standard does not require TCO. GCC/Clang/MSVC do tail-call optimisation when the recursive call is the last op at -O2/-O3, with no destructors after and compatible ABI.
Common mistakes
- ✗Returning
std::move(recursive_call())— disables TCO because of the wrapper - ✗Having a destructor run after the recursive call (e.g. local with non-trivial dtor)
- ✗Compiling debug and being surprised TCO doesn't fire
Follow-up questions
- →Why doesn't C++ standardise TCO?
- →How does
[[clang::musttail]]change the picture?
SeniorTheoryOccasionalWhat is the INVOKE operation, and what does std::invoke unify?
What is the INVOKE operation, and what does std::invoke unify?
INVOKE is the standard's abstract rule for calling any callable. std::invoke(f, args...) applies it uniformly: it calls free functions and functors as f(args...), but for a pointer-to-member it treats the first argument as the object, handling references, pointers, and reference_wrapper. It underpins std::bind, std::thread, and std::function.
Common mistakes
- ✗Hand-writing branchy code to call either a free function or a pointer-to-member when
std::invokealready unifies both - ✗Assuming
std::invokeadds runtime cost — it is fully resolved at compile time and inlines away - ✗Forgetting
std::invoke_result_t/std::is_invocable_vexist to query the same INVOKE rule in traits
Follow-up questions
- →How does
std::invoke_r(C++23) differ fromstd::invoke? - →Why do
std::bind,std::thread, andstd::asyncall specify their call in terms of INVOKE?
SeniorTheoryRareWhat is consteval and when should you use it instead of constexpr?
What is consteval and when should you use it instead of constexpr?
consteval (C++20) marks a function as immediate — every call must yield a constant expression. Unlike constexpr, no runtime fallback. Used for compile-time-only utilities.
Common mistakes
- ✗Marking a function
constevaland trying to call it with runtime arguments - ✗Taking a pointer to a
constevalfunction — error - ✗Confusing
constevalwithconstinit(which initialises a variable at compile time)
Follow-up questions
- →How does
constevalinteract withif consteval(C++23)? - →Why was
constevaladded ifconstexpralready supported compile-time evaluation?
SeniorTheoryRareWhat is C++23 "deducing this" and what does it solve?
What is C++23 "deducing this" and what does it solve?
C++23 lets a member take this as an explicit parameter: void f(this Self&& self, ...). Templated on value category and cv-qualification — replaces four overloads. Enables CRTP without inheritance.
Common mistakes
- ✗Trying to use
this->inside a deducing-this function —thisis gone, useself.instead - ✗Marking the function
virtual— incompatible - ✗Forgetting to forward: write
std::forward<Self>(self).memberto preserve value category
Follow-up questions
- →How does deducing-this remove the need for CRTP?
- →Why can't a deducing-this function be virtual?