C++
Renvoyer plusieurs valeurs d'une fonction
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
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:
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);
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
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
:
#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:
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);
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.
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 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:
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:
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}