Modern Approaches for Returning Multiple Values from C++ Functions

Nov 11, 2025 · Programming · 13 views · 7.8

Keywords: C++ | function return values | structured binding | tuple | modern C++

Abstract: This technical article comprehensively examines various methods for returning multiple values from C++ functions, with emphasis on modern C++ standards featuring structured bindings and tuple techniques. The paper provides detailed comparisons of reference parameters, structures, and pair/tuple approaches, supported by complete code examples demonstrating best practices across C++11, C++17, and other versions. Practical recommendations are offered considering code readability, type safety, and maintainability factors.

Introduction

Returning multiple values from functions represents a common requirement in C++ programming practice. Traditionally, developers often employed reference parameters or output parameters to achieve this objective, but these approaches exhibit significant limitations in terms of code clarity and maintainability. With the evolution of C++ standards, modern C++ provides more elegant and type-safe solutions.

Limitations of Traditional Approaches

The method of using reference parameters to return multiple values, while straightforward, presents several notable issues:

void divide(int dividend, int divisor, int& quotient, int& remainder) {
    quotient = dividend / divisor;
    remainder = dividend % divisor;
}

// Usage example
int q, r;
divide(14, 3, q, r);

The primary disadvantages of this approach include: function signatures cannot clearly distinguish between input and output parameters, callers must pre-declare variables, and return values cannot be used directly within expressions.

Improvements with Structure Approach

Defining dedicated structures to encapsulate return values significantly enhances code readability:

struct DivideResult {
    int quotient;
    int remainder;
};

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

// Usage example
auto result = divide(14, 3);
std::cout << result.quotient << ", " << result.remainder << std::endl;

This method clearly identifies the meaning of each return value through member names, avoiding dependency on ordering, but requires defining specialized structures for each return scenario.

Modern C++ Tuple Solutions

The std::tuple introduced in C++11 provides a generic solution for returning multiple values:

#include <tuple>

std::tuple<int, int> divide(int dividend, int divisor) {
    return std::make_tuple(dividend / divisor, dividend % divisor);
}

// C++11 usage
int quotient, remainder;
std::tie(quotient, remainder) = divide(14, 3);

The introduction of structured bindings in C++17 further simplifies tuple usage:

#include <tuple>

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

// C++17 structured binding
auto [quotient, remainder] = divide(14, 3);
std::cout << quotient << ", " << remainder << std::endl;

Handling Different Return Types

Tuple advantages become more pronounced when returning multiple values of different types:

#include <tuple>
#include <string>

std::tuple<bool, std::string, int> processData(const std::string& input) {
    if (input.empty()) {
        return {false, "Empty input", -1};
    }
    return {true, "Success", static_cast<int>(input.size())};
}

// Using structured binding
auto [success, message, size] = processData("hello");
if (success) {
    std::cout << message << ", size: " << size << std::endl;
}

Code Readability Considerations

While tuples provide generic solutions, dedicated structures may offer better code readability in certain scenarios. Consider this aviation calculation example:

// Using tuple - poor readability
std::tuple<double, double> calculateVelocity(double windSpeed, double windAzimuth,
                                            double planeAirspeed, double planeCourse);

// Using structure - clear intent
struct VelocityResult {
    double groundspeed;
    double course;
};

VelocityResult calculateVelocity(double windSpeed, double windAzimuth,
                               double planeAirspeed, double planeCourse);

Performance Considerations

Modern C++ compiler support for Return Value Optimization (RVO) and Named Return Value Optimization (NRVO) ensures that returning structures and tuples performs comparably to using output parameters. In most cases, prioritize code clarity and maintainability over minor performance differences.

Best Practice Recommendations

Based on technical analysis and practical experience, the following selection strategy is recommended:

  1. Simple Scenarios: For 2-3 return values of the same type, prefer std::pair or std::tuple with structured binding
  2. Complex Scenarios: When return values have clear business meaning, define dedicated structures or classes
  3. Modern Code: In projects supporting C++17 and above, fully leverage structured binding features
  4. API Design: Avoid output parameters, prefer return values to clearly express function contracts

Conclusion

Best practices for returning multiple values from C++ functions have evolved alongside language standard development. Modern C++'s structured binding and tuple technologies, combined with compiler optimization capabilities, provide developers with solutions that are both type-safe and highly expressive. When selecting specific approaches, comprehensively consider code readability, maintenance requirements, and performance needs. In most modern C++ projects, std::tuple with structured binding has become the preferred solution for returning multiple values.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.