Recherche…


Introduction

Il existe de nombreuses situations où il est utile de renvoyer plusieurs valeurs d'une fonction: par exemple, si vous souhaitez saisir un article et renvoyer le prix et le nombre en stock, cette fonctionnalité pourrait être utile. Il y a plusieurs façons de faire cela en C ++, et la plupart impliquent la STL. Cependant, si vous souhaitez éviter la STL pour une raison quelconque, il existe encore plusieurs façons de procéder, y compris les structs/classes et les arrays .

Utilisation des paramètres de sortie

Les paramètres peuvent être utilisés pour renvoyer une ou plusieurs valeurs; ces paramètres doivent être des pointeurs ou des références non const .

Les références:

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

Pointeurs:

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

Certaines bibliothèques ou frameworks utilisent un paramètre 'OUT' vide #define pour que les paramètres de sortie de la signature de la fonction soient clairement indiqués. Cela n'a aucun impact fonctionnel et sera compilé, mais rend la signature de la fonction un peu plus claire;

#define OUT

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

Utiliser std :: tuple

C ++ 11

Le type std::tuple peut regrouper un nombre quelconque de valeurs, incluant potentiellement des valeurs de différents types, en un seul objet de retour:

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

En C ++ 17, une liste d'initialisation contreventée peut être utilisée:

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

La récupération des valeurs du tuple renvoyé peut être fastidieuse, nécessitant l'utilisation de la fonction 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);

Si les types peuvent être déclarés avant que la fonction ne retourne, alors std::tie peut être utilisé pour décompresser un tuple dans des variables existantes:

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

Si l'une des valeurs renvoyées n'est pas nécessaire, std::ignore peut être utilisé:

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

Les liaisons structurées peuvent être utilisées pour éviter std::tie :

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

Si vous voulez retourner un tuple de références lvalue au lieu d'un tuple de valeurs, utilisez std::tie à la place de 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);
}

qui permet

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

Dans certains cas rares, vous utiliserez std::forward_as_tuple au lieu de std::tie ; soyez prudent si vous le faites, car les délais peuvent ne pas durer assez longtemps pour être consommés.

Utiliser std :: array

C ++ 11

Le conteneur std::array peut regrouper un nombre fixe de valeurs de retour. Ce nombre doit être connu à la compilation et toutes les valeurs de retour doivent être du même type:

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

Cela remplace les tableaux de style c de la forme int bar[4] . L'avantage étant que différentes fonctions std de c++ peuvent désormais être utilisées. Il fournit également des fonctions utiles comme membres at ce qui est une fonction d'accès membre en sécurité avec le contrôle lié, et la size qui vous permet de retourner la taille du tableau sans calcul.

Utiliser std :: pair

Le struct template std::pair peut regrouper exactement deux valeurs de retour, de deux types quelconques:

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

Avec C ++ 11 ou version ultérieure, une liste d'initialisation peut être utilisée à la place de std::make_pair :

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

Les valeurs individuelles de la std::pair retournée peuvent être récupérées en utilisant les first et second objets membres de la paire:

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

Sortie:

dix

En utilisant struct

Une struct peut être utilisée pour regrouper plusieurs valeurs de retour:

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

Au lieu d'affecter des champs individuels, un constructeur peut être utilisé pour simplifier la construction des valeurs renvoyées:

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

Les résultats individuels renvoyés par la fonction foo() peuvent être récupérés en accédant aux variables membres de la struct calc :

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

Sortie:

17 -7 60 0

Remarque: Lors de l'utilisation d'une struct , les valeurs renvoyées sont regroupées dans un seul objet et accessibles à l'aide de noms significatifs. Cela permet également de réduire le nombre de variables externes créées dans la portée des valeurs renvoyées.

C ++ 17

Pour décompresser une struct renvoyée par une fonction, des liaisons structurées peuvent être utilisées. Cela place les paramètres de sortie sur un pied d'égalité avec les paramètres in:

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

La sortie de ce code est identique à celle ci-dessus. La struct est toujours utilisée pour renvoyer les valeurs de la fonction. Cela vous permet de traiter les champs individuellement.

Fixations structurées

C ++ 17

C ++ 17 introduit des liaisons structurées, ce qui facilite encore la gestion de plusieurs types de retour, car vous n'avez pas besoin de vous appuyer sur std::tie() ni de décompresser manuellement un 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';
}

Les liaisons structurées peuvent être utilisées par défaut avec std::pair , std::tuple et tout type dont les membres de données non statiques sont tous des membres directs publics ou des membres d'une classe de base non ambiguë:

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;

Si vous faites votre type "tuple-like", il fonctionnera automatiquement avec votre type. Un tuple semblable est un type avec approprié tuple_size , tuple_element et s'écrit: 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");
    }
}

maintenant cela fonctionne:

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

Utilisation d'un objet de consommation

Nous pouvons fournir à un consommateur qui sera appelé les multiples valeurs pertinentes:

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'est ce qu'on appelle le "style de passage continu" .

Vous pouvez adapter une fonction renvoyant un tuple dans une fonction de style de continuation 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';
};

les versions plus complexes étant accessibles en écriture en C ++ 14 ou C ++ 11.

Utiliser std :: vector

Un std::vector peut être utile pour renvoyer un nombre dynamique de variables du même type. L'exemple suivant utilise int comme type de données, mais un std::vector peut contenir n'importe quel type trivialement copiable:

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

Utilisation de l'itérateur de sortie

Plusieurs valeurs du même type peuvent être renvoyées en passant un itérateur de sortie à la fonction. Ceci est particulièrement courant pour les fonctions génériques (comme les algorithmes de la bibliothèque standard).

Exemple:

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

Exemple d'utilisation:

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
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow