Returning several values from a function in C++ (C++23 edition)

Many programming languages such as the Go programming language are designed to make it easy to return several values at once from a function. In Go, it is often used to return an optional error code. The C++ programming language does not have a built-in support for returning several values. However, several standard types can serve the same purpose. If you need to return two values, you can use an std::pair instance. If you need to return two or more values, an std::tuple instance will do. With recent C++ standards, it works really well!

Suppose we want to compute a division with a string error message when one is trying to divided by zero:

std::tuple<int,std::string> divide(int a, int b) {
    if (b == 0) {
        return {0, "Error: Division by zero"};
    }
    return {a / b, "Success"};
}

This approach works nicely. The code is clear and readable.

You might be concerned that we are fixing the type (int). If you want to write one function for all integer types, you can do so with concepts, like so:

#include <concepts>


template <std::integral int_type>
std::tuple<int_type, std::string> divide(int_type a, int_type b) {
    if (b == 0) {
        return {0, "Error: Division by zero"};
    }
    return {a / b, "Success"};
}

With this definition, you can call divide(10,3), but if you try to call divide(10.0, 3.1323), you will get an error message such as “‘double’ does not satisfy ‘integral'”.

So far so good.

Prior to C++17, there was no elegant way to call this function and immediately capture the values without boilerplate code. Thankfully, we now have structured bindings:

auto [result, message] = divide(10, 2);
std::println("Result: {}, Message: {}", result, message);

As a bonus, in this example, I am using C++23’s std::println function which is available with GCC 14 and LLVM 19.

The structured binding approach (the keyword ‘auto’ followed by brackets) is nice, but what if you care about only one of the return values ? In C++23, you can use the underscore to indicate that you do not care about a value:

auto [quotient, _] = divide(15, 3);
std::println("Quotient only: {}", quotient);

What if you have existing variables, and you do not want to create new variables? You can use std::tie:

std::string msg = "Hello";
int i = 42;
std::tie(i,msg) = divide(3, 0);

You can thus write decent error handling code:

if (auto [res, err] = divide(8, 0); err != "Success") {
  std::println("Error occurred: {}", err);
} else {
  std::println("Result: {}", res);
}

You can do slightly better if the reason for returning two values was strictly to be able to return an error code. You can use std::expected.

Your division function might be written as…

template <std::integral int_type>
std::expected<int_type, std::string> divide(int_type a, int_type b) {
    if (b == 0) {
        return std::unexpected("Error: Division by zero");
    }
    return a / b;
}

It allows you to write slightly simpler error-handling code:

if (auto res1 = divide(10, 2)) {
  std::println("Result: {:>4}, Message: Success", res1.value());
} else {
  std::println("Error: {}", res1.error());
}

Another benefit of std::expected is that you can let the calling code choose the default value (in case of error):

int quotient = divide(15, 3).value_or(0);
std::println("Quotient only: {:>4}", quotient);

If you prefer, you can use std::optional, which works much the same way:

std::optional<int> divide(int a, int b) {
    if (b == 0) {
        return std::nullopt;
    }
    return a / b;
}

However, std::optional is less appropriate for error handling: you cannot pass an error code or an error message. The std::optional strategy is meant for the simple case where a value can be omitted.

In C++, returning multiple values from functions has evolved significantly, offering developers flexible and elegant solutions. From using std::pair and std::tuple for general-purpose multi-value returns to leveraging std::expected for robust error handling, and std::optional for optional values. By adopting these techniques, C++ programmers can make their code easier to read and to maintain.

 

Further reading:

Care is needed to use C++ std::optional with non-trivial objects

Further: CppCon 2016: Patrice Roy “The Exception Situation”

Daniel Lemire, "Returning several values from a function in C++ (C++23 edition)," in Daniel Lemire's blog, May 18, 2025, https://lemire.me/blog/2025/05/18/returning-several-values-from-a-function-in-c-c23-edition/.
[BibTeX]

Published by

Daniel Lemire

A computer science professor at the University of Quebec (TELUQ).

12 thoughts on “Returning several values from a function in C++ (C++23 edition)”

  1. What is three current consensus on overhead of std::expected? Is it ready to be declared as the error reporting facility, alike to rust, in some sense even deprecating exceptions?

    1. Whether you want to use exceptions or not is context dependent. I personnally prefer to avoid exceptions, but it invariably makes the code slightly more convoluted.

      1. Hoping that one day we will get multiple return value feature like python’s in C++ where we don’t need to create any tuple or pair just simply return values

        1. Python automatically creates a tuple when you do that. Ultimately, any function in any language returns 1 value. That value can be a compound type, like a struct or a tuple. But it’s 1 value. Some languages just provide a simpler syntax to do so.

          1. That’s not really true. Typically, there’s a return instruction that sets the instruction pointer to a value from the stack. Any communication between the caller and the callee, for either passing in argument values or passing back return values, must be implemented using additional instructions. There are endless possibilities for calling conventions.

        2. You can already do that, use auto as the return type and use std::make_tuple at the return sites. Largest drawback is that you have to be very careful about type deduction because if you have both return std::make_tuple(5, “foo”); and std::string bar = “bar”; return std::make_tuple(6, bar); the first will be deduced as while the second will be .

  2. I’m wondering about this from the stand point of security, because it’s weird to me as people have been using pointered arguments for this for ages, for example just look at Microsoft’s documentation on many kernel level functions…
    Since using additional pointer-type arguments for additional return endpoints is essentially the same as returning an N-ary tuple and it’s usually much easier to handle errors with (I.E. return an NTSTATUS, all other intended return value would be copied or placed in the pointer-type argument).

    Since return values pass by some arbitrary move implementation, you could do the same within the function and just declare the pointer before the function rather than when calling it.

    Would love to hear your opinion.

  3. I tried out the V lang a little. Boring but in an excellent way, in that everything worked as intuitively expected. Gave it up for the moment because still poor compiled code speed.
    Anyway I doubled the speed of the fast (Walsh) Hadamard transform in pure Java by unrolling the loops, for better auto-vectorization. JNI is almost depreciated, so…

    https://archive.org/details/mini-java-collection-3
    https://archive.org/details/mini-java-collection-2
    https://archive.org/details/mini-java-collection

Leave a Reply

Your email address will not be published.

You can also subscribe by email to this blog (non-commercial, no ads, weekly email).

How to post code (C, C++, Java, Python, etc.):

Wrap your code in backticks, like this:

`int main() {
    return 0;
}`