Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A C++ function returns one expression, but that expression can be an object containing several results. For most APIs, return a small named struct; use std::pair or std::tuple for compact, fixed groups, then unpack them with structured bindings in C++17 or later. Output-reference parameters remain useful when caller-owned storage or a legacy interface is part of the design.

Return one object that contains the results

C++ does not give a function multiple independent return statements at once. Instead, the function returns one object, and that object can hold multiple values. Microsoft Learn describes several ways to do this, including a named class or struct, std::pair, std::tuple, and pass-by-reference output parameters (Microsoft Learn: Returning Values).

For example, integer division produces a quotient and a remainder. Both can be represented by one result object:

struct DivisionResult {
    int quotient;
    int remainder;
};

DivisionResult divide(int dividend, int divisor) {
    return {dividend / divisor, dividend % divisor};
}

auto result = divide(17, 5);
// result.quotient == 3
// result.remainder == 2

The function returns a single DivisionResult. Its named members make each value’s purpose clear wherever the result is used.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose the result type that fits the relationship

Use a named struct for meaningful or evolving results

A named type is usually the clearest choice when the values have distinct meanings, the result is part of a public API, or the set of results may grow. Callers write result.quotient rather than relying on a positional index such as std::get<0>(result). Adding another named member can also make an expanded contract easier to understand.

Use std::pair for exactly two related values

std::pair, declared in <utility>, holds two values. It suits results that naturally come as a pair, such as an iterator and an insertion flag, or a quotient and remainder:

#include <utility>

std::pair<int, int> divide_pair(int dividend, int divisor) {
    return {dividend / divisor, dividend % divisor};
}

Without structured bindings, access the values as first and second. Those names are generic, so a pair is less self-documenting than a domain-specific struct when the values’ roles are not immediately obvious. The standard library defines std::pair as a two-element container (cppreference: std::pair).

Use std::tuple for a fixed group of values

std::tuple, declared in <tuple>, can hold a fixed number of values with different types:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#include <string>
#include <tuple>

std::tuple<int, std::string, double> read_record() {
    return {108, "Some text", 0.01};
}

A tuple works well for a local result that will be unpacked immediately or when an API already uses tuple-like data. Otherwise, access such as std::get<0>(record) leaves readers to remember what each position means. The standard library documents both pair and tuple as ways to package multiple values (cppreference: std::tuple).

Unpack the returned object at the call site

Structured bindings in C++17 and later

Structured bindings give names to the elements of a returned pair or tuple. They also work with suitable aggregates, including structs with public data members:

const auto [quotient, remainder] = divide_pair(17, 5);
// C++17 or later

The declaration makes the components convenient to use; it does not create a second return channel. The function still returns one object. Structured bindings were introduced in C++17 (cppreference: Structured binding declaration).

Choose the binding names in the same order as the object’s elements. With a tuple or pair, callers depend on that order, so changing it can break their assumptions even if the types remain the same.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

std::tie when the destination variables already exist

If variables are already declared, std::tie assigns tuple-like elements into them. It is available from <tuple> in C++11:

#include <tuple>

int quotient;
int remainder;
std::tie(quotient, remainder) = divide_pair(17, 5);

std::tie creates a tuple of lvalue references. Use std::ignore when a component is not needed:

bool inserted;
std::tie(std::ignore, inserted) = some_set.insert(value);

This approach requires destination lvalues to exist first. Microsoft Learn and cppreference document std::tie for tying references to tuple elements and ignoring selected values (Microsoft Learn: tuple functions; cppreference: std::tie).

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

When output-reference parameters make sense

A function can write results into references supplied by its caller:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Best Value
void divide_out(int dividend, int divisor,
                int& quotient, int& remainder) {
    quotient = dividend / divisor;
    remainder = dividend % divisor;
}

This can be appropriate when the interface intentionally modifies caller-owned objects, reuses caller-provided storage, must preserve an established legacy signature, or is shaped by an ABI constraint. In ordinary new code, a returned aggregate tends to make the data flow easier to see: the result comes back as the function’s value rather than through parameters that are changed as a side effect.

If an output-parameter function can fail, its contract should make clear what happens to each output on failure. A returned result type can also represent status alongside values, but whichever design is chosen, document whether outputs are valid when the operation does not succeed.

Compare the approaches

Approach Best fit Main trade-off Minimum language level
Named struct Stable, self-documenting domain result Requires defining a type C++98
std::pair Exactly two naturally related values Generic members are first and second C++98
std::tuple Fixed group of values, possibly of different types Elements are positional C++11
Structured bindings Readable unpacking of a returned object Declaration syntax is unavailable before C++17 C++17
std::tie Assigning into existing variables or ignoring elements Requires predeclared lvalues C++11
Output references Caller-owned storage or an intentional legacy interface Mutation is less visible in the return value C++98

Check these correctness details

  • Return a value on every reachable path. A value-returning function that falls off the end has undefined behavior, except for the language’s specified cases such as main and certain coroutine rules (cppreference: return statement).
  • Do not return references to local variables. A local object ceases to exist when the function returns. Return an object by value, or return a reference only when the referenced object’s lifetime is guaranteed by the interface.
  • Keep positional meanings stable. Callers using std::get<N>, structured bindings, or std::tie rely on element order. Prefer named fields when that order is not self-evident or likely to change.
  • Match syntax to the selected standard. std::pair is available from C++98, std::tuple and std::tie from C++11, and structured bindings from C++17. Set the compiler’s language standard accordingly.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.