Return Values
Return Value Optimization (RVO)
Modern compilers use copy elision (including RVO and NRVO) to remove temporary objects when returning by value. Since C++17, copy elision is guaranteed in certain cases (e.g., returning a prvalue created in the return statement). In practice, returning large objects by value is usually efficient.
#include <vector>
#include <string>
// Named Return Value Optimization (NRVO) – commonly elided but not guaranteed
std::vector<std::string> loadNames() {
std::vector<std::string> names;
// ... fill vector
return names; // NRVO candidate
}
// Guaranteed copy elision in C++17+: returning a prvalue temporary
std::vector<std::string> makeNames() {
return std::vector<std::string>{"Ana", "Ben", "Chi"};
}
int main() {
auto a = loadNames();
auto b = makeNames();
return 0;
}
Multiple Return Values
C++ doesn’t have native multiple return values, but several idioms provide this functionality. Prefer structs (named fields) for readability, or tuples/pairs for quick returns.
#include <tuple>
#include <string>
// 1) Using std::tuple
auto getStats() {
double avg = 10.5, minVal = 2.1, maxVal = 25.0;
return std::make_tuple(avg, minVal, maxVal);
}
// 2) Using output parameters
struct Result { int value; };
void computeResults(Result& res1, Result& res2) {
res1.value = 10;
res2.value = 20;
}
// 3) Structured bindings (C++17)
auto calculateMetrics() {
return std::make_tuple(5.0, 7.5);
}
// 4) Returning a named struct for clarity
struct Metrics { double mean; double median; };
Metrics betterMetrics() { return {5.0, 7.5}; }
int main() {
auto [avg, minVal, maxVal] = getStats();
Result r1{}, r2{}; computeResults(r1, r2);
auto [mean, median] = calculateMetrics();
Metrics m = betterMetrics();
(void)avg; (void)minVal; (void)maxVal; (void)mean; (void)median; (void)m;
return 0;
}
Return Type Deduction
Since C++14, functions can use auto return type deduction. If you need to preserve references or cv-qualifiers from the return expression, use decltype(auto)
.
#include <iostream>
#include <string>
// Generic multiply with auto return type deduction (C++14)
template <typename T, typename U>
auto multiply(T a, U b) {
return a * b; // decays to a value type
}
// Preserve reference with decltype(auto)
template <typename T>
decltype(auto) front_of(T&& container) {
return (container.front()); // returns by reference if front() does
}
int main() {
std::cout << multiply(3, 4) << '\n'; // int * int -> int
std::cout << multiply(2.5, 4) << '\n'; // double * int -> double
std::string s = "abc";
auto&& ref = front_of(s); // ref is char&
ref = 'A';
std::cout << s << '\n'; // prints "Abc"
return 0;
}
Error Handling
Instead of special return values, C++17+ offers safer alternatives like std::optional
. (In C++23, consider std::expected
for value-or-error results.)
#include <optional>
#include <iostream>
std::optional<int> safeDivide(int a, int b) {
if (b == 0) return std::nullopt;
return a / b;
}
int main() {
auto result = safeDivide(10, 2);
if (result)
std::cout << "Result: " << *result << '\n';
else
std::cout << "Division by zero!\n";
return 0;
}
What Not to Return (Lifetimes)
Never return references or views to temporaries or to local variables. Prefer returning by value (leveraging RVO) or ensure the referenced data outlives the function.
#include <string>
#include <string_view>
// BAD: returns reference to a local (dangling)
const std::string& bad_ref() {
std::string s = "tmp";
return s; // dangling reference
}
// BAD: returns view to a temporary (dangling)
std::string_view bad_view() {
return std::string("tmp"); // dangling view
}
// GOOD: return by value (cheap with RVO)
std::string good_value() { return "ok"; }
// GOOD: return a view only to caller-owned data
std::string_view head3(const std::string& s) {
return std::string_view{s}.substr(0, 3);
}
Guidelines
- Prefer returning by value; rely on copy elision and moves.
- Use tuples/pairs for quick multi-values; prefer structs for named fields.
- Use auto
for simple deduction; use decltype(auto)
when you intend to return references.
- Use std::optional
(and C++23 std::expected
) instead of sentinel error codes.
- Avoid returning references/views to data that won’t outlive the function.