Поиск…


Вступление

Существует много ситуаций, когда полезно возвращать несколько значений из функции: например, если вы хотите ввести элемент и вернуть цену и количество на складе, эта функция может быть полезна. Есть много способов сделать это на C ++, и большинство из них связано с STL. Однако, если вы хотите избежать STL по какой-то причине, есть еще несколько способов сделать это, включая structs/classes и arrays .

Использование выходных параметров

Параметры могут использоваться для возврата одного или нескольких значений; эти параметры должны быть const указателями или ссылками.

Рекомендации:

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

указатели:

void calculate(int a, int b, int* c, int* d, int* e, int* f) {
    *c = a + b;
    *d = a - b;
    *e = a * b;
    *f = a / b;
}

Некоторые библиотеки или фреймворки используют пустой 'OUT' #define чтобы сделать его совершенно очевидным, какие параметры являются выходными параметрами в сигнатуре функции. Это не имеет никакого функционального воздействия и будет скомпилировано, но делает подпись функции более понятной;

#define OUT

void calculate(int a, int b, OUT int& c) {
    c = a + b;
}

Использование std :: tuple

C ++ 11

Тип std::tuple может связывать любое количество значений, потенциально включающих значения разных типов, в один возвращаемый объект:

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);
}

В C ++ 17 можно использовать список упрощенных инициализаторов:

C ++ 17
std::tuple<int, int, int, int> foo(int a, int b)    {
    return {a + b, a - b, a * b, a / b};
}

Получение значений из возвращаемого tuple может быть громоздким, требуя использования функции std::get template:

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);

Если типы могут быть объявлены до возвращения функции, то std::tie можно использовать для распаковки tuple в существующие переменные:

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

Если одно из возвращаемых значений не требуется, можно использовать std::ignore :

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

Структурные привязки можно использовать, чтобы избежать std::tie :

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

Если вы хотите вернуть кортеж ссылок lvalue вместо кортежа значений, используйте std::tie вместо std::make_tuple .

std::tuple<int&, int&> minmax( int& a, int& b ) {
  if (b<a)
    return std::tie(b,a);
  else
    return std::tie(a,b);
}

который разрешает

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

В некоторых редких случаях вы будете использовать std::forward_as_tuple вместо std::tie ; будьте осторожны, если вы это сделаете, поскольку временные периоды могут длиться недостаточно долго, чтобы их можно было потреблять.

Использование std :: array

C ++ 11

Контейнер std::array может связывать фиксированное число возвращаемых значений. Этот номер должен быть известен во время компиляции, и все возвращаемые значения должны быть одного типа:

std::array<int, 4> bar(int a, int b) {
    return { a + b, a - b, a * b, a / b };
}

Это заменяет c массивами стиля формы int bar[4] . Преимущество состоит в том, что теперь можно использовать различные функции std c++ . Он также содержит полезные функции - членов , как at котором является безопасной функцией доступа члена с связанной проверкой, и size , который позволяет вернуть размер массива без расчета.

Использование std :: pair

Структурный шаблон std::pair может связывать ровно два возвращаемых значения любых двух типов:

#include <utility>
std::pair<int, int> foo(int a, int b) {
    return std::make_pair(a+b, a-b);
}

С C ++ 11 или более поздней std::make_pair вместо std::make_pair можно использовать список инициализаторов:

C ++ 11
#include <utility>
std::pair<int, int> foo(int a, int b) {
    return {a+b, a-b};
}

Отдельные значения возвращенной std::pair могут быть получены с использованием first и second объектов-членов пары:

std::pair<int, int> mrvs = foo(5, 12);
std::cout << mrvs.first + mrvs.second << std::endl;

Выход:

10

Использование структуры

struct можно использовать для объединения нескольких возвращаемых значений:

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};
}

auto calc = foo(5, 12);
C ++ 11

Вместо назначения отдельным полям конструктор может использоваться для упрощения построения возвращаемых значений:

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);

Отдельные результаты, возвращаемые функцией foo() могут быть получены путем доступа к переменным-членам struct calc :

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

Выход:

17 -7 60 0

Примечание. При использовании struct возвращаемые значения группируются вместе в одном объекте и доступны с использованием значимых имен. Это также помогает уменьшить количество посторонних переменных, созданных в области возвращаемых значений.

C ++ 17

Чтобы распаковать struct возвращаемую функцией, можно использовать структурированные привязки . Это приводит к тому, что параметры вывода на четной основе имеют параметры:

int a=5, b=12;
auto[add, sub, mul, div] = foo(a, b);
std::cout << add << ' ' << sub << ' ' << mul << ' ' << div << '\n';

Результат этого кода идентичен выходу. struct по - прежнему используется для возврата значения из функции. Это позволяет вам обрабатывать поля отдельно.

Структурированные привязки

C ++ 17

В C ++ 17 реализованы структурированные привязки, что упрощает работу с несколькими типами возвращаемых данных, так как вам не нужно полагаться на std::tie() или выполнять ручную распаковку:

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';
}

Структурированные привязки могут использоваться по умолчанию с помощью std::pair , std::tuple и любого типа, чьи нестатические члены данных являются либо публичными прямыми членами, либо членами недвусмысленного базового класса:

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;

Если вы сделаете свой тип «tuple-like», он также автоматически будет работать с вашим типом. Кортеж, как тип с соответствующим tuple_size , tuple_element и get написано:

namespace my_ns {
    struct my_type {
        int x;
        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");
    }
}

теперь это работает:

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';
}

Использование функции Object Consumer

Мы можем предоставить потребителю, который будет вызываться с несколькими соответствующими значениями:

C ++ 11
template <class F>
void foo(int a, int b, F consumer) {
    consumer(a + b, a - b, a * b, a / b);
}

// use is simple... ignoring some results is possible as well
foo(5, 12, [](int sum, int , int , int ){
    std::cout << "sum is " << sum << '\n';
});

Это называется «стиль продолжения прохождения» .

Вы можете адаптировать функцию, возвращающую кортеж, в функцию продолжения прохождения через:

C ++ 17
template<class Tuple>
struct continuation {
  Tuple t;
  template<class F>
  decltype(auto) operator->*(F&& f)&&{
    return std::apply( std::forward<F>(f), std::move(t) );
  }
};
std::tuple<int,int,int,int> foo(int a, int b);

continuation(foo(5,12))->*[](int sum, auto&&...) {
  std::cout << "sum is " << sum << '\n';
};

причем более сложные версии записываются на C ++ 14 или C ++ 11.

Использование std :: vector

std::vector может быть полезен для возврата динамического числа переменных того же типа. Следующий пример использует int как тип данных, но std::vector может содержать любой тип, который тривиально можно копировать:

#include <vector>
#include <iostream>

// the following function returns all integers between and including 'a' and 'b' in a vector
// (the function can return up to std::vector::max_size elements with the vector, given that
// the system's main memory can hold that many items)
std::vector<int> fillVectorFrom(int a, int b) {
    std::vector<int> temp;
    for (int i = a; i <= b; i++) {
        temp.push_back(i);
    }
    return temp;
}

int main() {    
    // assigns the filled vector created inside the function to the new vector 'v'
    std::vector<int> v = fillVectorFrom(1, 10);

    // prints "1 2 3 4 5 6 7 8 9 10 "
    for (int i = 0; i < v.size(); i++) {
        std::cout << v[i] << " ";
    }
    std::cout << std::endl;
    return 0;
}

Использование выходного итератора

Несколько значений одного и того же типа могут быть возвращены путем передачи выходного итератора в функцию. Это особенно характерно для общих функций (таких как алгоритмы стандартной библиотеки).

Пример:

template<typename Incrementable, typename OutputIterator>
void generate_sequence(Incrementable from, Incrementable to, OutputIterator output) {
    for (Incrementable k = from; k != to; ++k)
        *output++ = k;
}

Пример использования:

std::vector<int> digits;
generate_sequence(0, 10, std::back_inserter(digits));
// digits now contains {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow