Sök…


Introduktion

Det finns många situationer där det är användbart att returnera flera värden från en funktion: till exempel om du vill mata in en artikel och returnera priset och antalet i lager, kan denna funktion vara användbar. Det finns många sätt att göra detta i C ++, och de flesta involverar STL. Men om du vill undvika STL av någon anledning finns det fortfarande flera sätt att göra detta, inklusive structs/classes och arrays .

Använda utgångsparametrar

Parametrar kan användas för att returnera ett eller flera värden; dessa parametrar måste vara icke- const pekare eller referenser.

referenser:

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

pekare:

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

Vissa bibliotek eller ramverk använder en tom 'OUT' #define att göra det riktigt uppenbart vilka parametrar som är utgångsparametrar i funktionssignaturen. Detta har ingen funktionell påverkan och kommer att sammanställas, men gör funktionssignaturen lite tydligare;

#define OUT

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

Använda std :: tuple

C ++ 11

Typen std::tuple kan paketera valfritt antal värden, eventuellt inkluderande värden av olika typer, i ett enda returobjekt:

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

I C ++ 17 kan en listad initialiseringslista användas:

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

Att hämta värden från den returnerade tuple kan vara besvärlig, vilket kräver användning av std::get template-funktionen:

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

Om typerna kan deklareras innan funktionen återgår, kan std::tie användas för att packa upp en tuple i befintliga variabler:

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

Om ett av de returnerade värdena inte behövs, kan std::ignore användas:

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

Strukturerade bindningar kan användas för att undvika std::tie :

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

Om du vill returnera en tupel med referensvärden i stället för en tupel med värden, använd std::tie istället för 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);
}

vilket tillåter

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

I vissa sällsynta fall använder du std::forward_as_tuple istället för std::tie ; var försiktig om du gör det, eftersom tillfälliga kanske inte håller tillräckligt länge för att konsumeras.

Använda std :: array

C ++ 11

Containern std::array kan buntas samman ett fast antal returvärden. Detta nummer måste vara känt vid sammanställningstid och alla returvärden måste vara av samma typ:

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

Detta ersätter matriser i c-stil av formens int bar[4] . Fördelen är att olika c++ std-funktioner nu kan användas på den. Det ger också användbara medlemsfunktioner som at vilken är en säker medlem åtkomstfunktion med bunden kontroll och size som gör att du kan återställa storleken på matrisen utan beräkning.

Använda std :: par

Strukturmallen std::pair kan buntas exakt två returvärden, av två typer:

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

Med C ++ 11 eller senare kan en initialiseringslista användas istället för std::make_pair :

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

De individuella värdena för det returnerade std::pair kan hämtas genom att använda parets first och second medlemsobjekt:

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

Produktion:

10

Använda strukt

En struct kan användas för att paketera flera avkastningsvärden:

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

I stället för tilldelning till enskilda fält kan en konstruktör användas för att förenkla konstruktionen av returnerade värden:

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

De enskilda resultaten som returneras av funktionen foo() kan hämtas genom att få åtkomst till medlemsvariablerna i struct calc :

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

Produktion:

17 -7 60 0

Obs: När du använder en struct grupperas de returnerade värdena i ett enda objekt och är tillgängliga med meningsfulla namn. Detta hjälper också till att minska antalet främmande variabler som skapats inom ramen för de returnerade värdena.

C ++ 17

För att packa upp en struct returneras från en funktion kan strukturerade bindningar användas. Detta placerar utparametrarna på ett jämnt sätt med in-parametrarna:

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

Utgången från denna kod är identisk med den ovan. struct används fortfarande för att returnera värdena från funktionen. Detta gör att du kan hantera fälten individuellt.

Strukturerade bindningar

C ++ 17

C ++ 17 introducerar strukturerade bindningar, vilket gör det ännu enklare att hantera flera returtyper, eftersom du inte behöver lita på std::tie() eller göra någon manuell uppackning av tuple:

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

Strukturerade bindningar kan användas som standard med std::pair , std::tuple och alla typer vars icke-statiska datamedlemmar är antingen offentliga direktmedlemmar eller medlemmar i en entydig basklass:

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;

Om du gör din typ "tupel-liknande" fungerar det också automatiskt med din typ. En tupel liknande är en typ med lämplig tuple_size , tuple_element och get skrivit:

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

nu fungerar detta:

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

Använda en funktionskonsument

Vi kan tillhandahålla en konsument som kommer att anropas med flera relevanta värden:

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

Detta kallas "fortsättning som passerar stil" .

Du kan anpassa en funktion som returnerar en tuple till en fortsättning som passerar stilfunktionen via:

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

med mer komplexa versioner som kan skrivas i C ++ 14 eller C ++ 11.

Använda std :: vektor

En std::vector kan vara användbar för att returnera ett dynamiskt antal variabler av samma typ. Följande exempel använder int som datatyp, men en std::vector kan innehålla alla typer som är trivialt kopierbara:

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

Använda Output Iterator

Flera värden av samma typ kan returneras genom att skicka en utgångs iterator till funktionen. Detta är särskilt vanligt för generiska funktioner (som standardbibliotekets algoritmer).

Exempel:

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

Exempel på användning:

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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow