Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CPlusPlusNotesForProfessionals.pdf
Скачиваний:
47
Добавлен:
20.05.2023
Размер:
5.11 Mб
Скачать

Chapter 24: Returning several values from a function

There are many situations where it is useful to return several values from a function: for example, if you want to input an item and return the price and number in stock, this functionality could be useful. There are many ways to do this in C++, and most involve the STL. However, if you wish to avoid the STL for some reason, there are still several ways to do this, including structs/classes and arrays.

Section 24.1: Using std::tuple

Version ≥ C++11

The type std::tuple can bundle any number of values, potentially including values of di erent types, into a single return object:

std::tuple<int, int, int, int> foo(int a, int b) { // or auto (C++14) return std::make_tuple(a + b, a - b, a * b, a / b);

}

In C++17, a braced initializer list can be used:

Version ≥ C++17

std::tuple<int, int, int, int> foo(int a, int b)

{

return {a + b, a - b, a * b, a / b};

 

}

 

 

 

Retrieving values from the returned tuple can be cumbersome, requiring the use of the std::get template function:

auto mrvs = foo(5, 12);

auto add = std::get<0>(mrvs); auto sub = std::get<1>(mrvs); auto mul = std::get<2>(mrvs); auto div = std::get<3>(mrvs);

If the types can be declared before the function returns, then std::tie can be employed to unpack a tuple into existing variables:

int add, sub, mul, div;

std::tie(add, sub, mul, div) = foo(5, 12);

If one of the returned values is not needed, std::ignore can be used:

std::tie(add, sub, std::ignore, div) = foo(5, 12);

Version ≥ C++17

Structured bindings can be used to avoid std::tie:

auto [add, sub, mul, div] = foo(5,12);

If you want to return a tuple of lvalue references instead of a tuple of values, use std::tie in place of std::make_tuple.

std::tuple<int&, int&> minmax( int& a, int& b ) {

GoalKicker.com – C++ Notes for Professionals

122

if (b<a)

return std::tie(b,a); else

return std::tie(a,b);

}

which permits

void increase_least(int& a, int& b) { std::get<0>(minmax(a,b))++;

}

In some rare cases you'll use std::forward_as_tuple instead of std::tie; be careful if you do so, as temporaries may not last long enough to be consumed.

Section 24.2: Structured Bindings

Version ≥ C++17

C++17 introduces structured bindings, which makes it even easier to deal with multiple return types, as you do not need to rely upon std::tie() or do any manual tuple unpacking:

std::map<std::string, int> m;

// insert an element into the map and check if insertion succeeded auto [iterator, success] = m.insert({"Hello", 42});

if (success) {

// your code goes here

}

// iterate over all elements without having to use the cryptic 'first' and 'second' names for (auto const& [key, value] : m) {

std::cout << "The value for " << key << " is " << value << '\n';

}

Structured bindings can be used by default with std::pair, std::tuple, and any type whose non-static data

members are all either public direct members or members of an unambiguous base class:

struct A { int x; }; struct B : A { int y; }; B foo();

//with structured bindings const auto [x, y] = foo();

//equivalent code without structured bindings const auto result = foo();

auto& x = result.x; auto& y = result.y;

If you make your type "tuple-like" it will also automatically work with your type. A tuple-like is a type with appropriate tuple_size, tuple_element and get written:

namespace my_ns { struct my_type {

int x;

GoalKicker.com – C++ Notes for Professionals

123

double d; std::string s;

};

struct my_type_view { my_type* ptr;

};

}

namespace std { template<>

struct tuple_size<my_ns::my_type_view> : std::integral_constant<std::size_t, 3> {};

template<> struct tuple_element<my_ns::my_type_view, 0>{ using type = int; }; template<> struct tuple_element<my_ns::my_type_view, 1>{ using type = double; }; template<> struct tuple_element<my_ns::my_type_view, 2>{ using type = std::string; };

}

namespace my_ns { template<std::size_t I>

decltype(auto) get(my_type_view const& v) { if constexpr (I == 0)

return v.ptr->x;

else if constexpr (I == 1) return v.ptr->d;

else if constexpr (I == 2) return v.ptr->s;

static_assert(I < 3, "Only 3 elements");

}

}

now this works:

my_ns::my_type t{1, 3.14, "hello world"};

my_ns::my_type_view foo() { return {&t};

}

int main() {

auto[x, d, s] = foo();

std::cout << x << ',' << d << ',' << s << '\n';

}

Section 24.3: Using struct

A struct can be used to bundle multiple return values:

Version ≥ C++11

struct foo_return_type { int add;

int sub; int mul; int div;

};

foo_return_type foo(int a, int b) { return {a + b, a - b, a * b, a / b};

}

GoalKicker.com – C++ Notes for Professionals

124

auto calc = foo(5, 12);

Version < C++11

Instead of assignment to individual fields, a constructor can be used to simplify the constructing of returned values:

struct foo_return_type { int add;

int sub; int mul; int div;

foo_return_type(int add, int sub, int mul, int div) : add(add), sub(sub), mul(mul), div(div) {}

};

foo_return_type foo(int a, int b) {

return foo_return_type(a + b, a - b, a * b, a / b);

}

foo_return_type calc = foo(5, 12);

The individual results returned by the function foo() can be retrieved by accessing the member variables of the struct calc:

std::cout << calc.add << ' ' << calc.sub << ' ' << calc.mul << ' ' << calc.div << '\n';

Output:

17 -7 60 0

Note: When using a struct, the returned values are grouped together in a single object and accessible using meaningful names. This also helps to reduce the number of extraneous variables created in the scope of the returned values.

Version ≥ C++17

In order to unpack a struct returned from a function, structured bindings can be used. This places the outparameters on an even footing with the in-parameters:

int a=5, b=12;

auto[add, sub, mul, div] = foo(a, b);

std::cout << add << ' ' << sub << ' ' << mul << ' ' << div << '\n';

The output of this code is identical to that above. The struct is still used to return the values from the function. This permits you do deal with the fields individually.

Section 24.4: Using Output Parameters

Parameters can be used for returning one or more values; those parameters are required to be non-const pointers or references.

References:

void calculate(int a, int b, int& c, int& d, int& e, int& f) { c = a + b;

d = a - b;

GoalKicker.com – C++ Notes for Professionals

125