Il existe de nombreuses façons de renvoyer plusieurs paramètres. Je vais être exagéré.
Utilisez des paramètres de référence:
void foo( int& result, int& other_result );
utiliser les paramètres du pointeur:
void foo( int* result, int* other_result );
ce qui a l'avantage que vous devez faire &
sur le site d'appel, alertant peut-être les gens que c'est un paramètre externe.
Écrivez un modèle et utilisez-le:
template<class T>
struct out {
std::function<void(T)> target;
out(T* t):target([t](T&& in){ if (t) *t = std::move(in); }) {}
out(std::optional<T>* t):target([t](T&& in){ if (t) t->emplace(std::move(in)); }) {}
out(std::aligned_storage_t<sizeof(T), alignof(T)>* t):
target([t](T&& in){ ::new( (void*)t ) T(std::move(in)); } ) {}
template<class...Args> // TODO: SFINAE enable_if test
void emplace(Args&&...args) {
target( T(std::forward<Args>(args)...) );
}
template<class X> // TODO: SFINAE enable_if test
void operator=(X&&x){ emplace(std::forward<X>(x)); }
template<class...Args> // TODO: SFINAE enable_if test
void operator()(Args...&&args){ emplace(std::forward<Args>(args)...); }
};
alors nous pouvons faire:
void foo( out<int> result, out<int> other_result )
et tout va bien. foo
n'est plus en mesure de lire les valeurs transmises en bonus.
D'autres façons de définir un emplacement où vous pouvez placer des données peuvent être utilisées pour construire out
. Un rappel pour mettre des choses quelque part, par exemple.
Nous pouvons renvoyer une structure:
struct foo_r { int result; int other_result; };
foo_r foo();
whick fonctionne bien dans chaque version de C ++, et c ++ 17 cela permet également:
auto&&[result, other_result]=foo();
à coût nul. Les paramètres ne peuvent même pas être déplacés grâce à l'élision garantie.
Nous pourrions retourner un std::tuple
:
std::tuple<int, int> foo();
qui a l'inconvénient que les paramètres ne sont pas nommés. Cela permet auc ++ 17:
auto&&[result, other_result]=foo();
ainsi que. Antérieur àc ++ 17 on peut plutôt faire:
int result, other_result;
std::tie(result, other_result) = foo();
ce qui est un peu plus gênant. L'élision garantie ne fonctionne cependant pas ici.
En entrant dans un territoire étranger (et c'est après out<>
!), Nous pouvons utiliser le style de passage de continuation:
void foo( std::function<void(int result, int other_result)> );
et maintenant les appelants font:
foo( [&](int result, int other_result) {
/* code */
} );
un avantage de ce style est que vous pouvez retourner un nombre arbitraire de valeurs (avec un type uniforme) sans avoir à gérer la mémoire:
void get_all_values( std::function<void(int)> value )
le value
rappel pourrait être appelé 500 fois lorsque vousget_all_values( [&](int value){} )
.
Pour la folie pure, vous pouvez même utiliser une suite sur la suite.
void foo( std::function<void(int, std::function<void(int)>)> result );
dont l'utilisation ressemble à:
foo( [&](int result, auto&& other){ other([&](int other){
/* code */
}) });
qui permettrait des relations multiples entre result
etother
.
Encore une fois avec des valeurs uniforn, nous pouvons le faire:
void foo( std::function< void(span<int>) > results )
ici, nous appelons le rappel avec une gamme de résultats. Nous pouvons même le faire à plusieurs reprises.
En utilisant cela, vous pouvez avoir une fonction qui transmet efficacement les mégaoctets de données sans faire d'allocation hors de la pile.
void foo( std::function< void(span<int>) > results ) {
int local_buffer[1024];
std::size_t used = 0;
auto send_data=[&]{
if (!used) return;
results({ local_buffer, used });
used = 0;
};
auto add_datum=[&](int x){
local_buffer[used] = x;
++used;
if (used == 1024) send_data();
};
auto add_data=[&](gsl::span<int const> xs) {
for (auto x:xs) add_datum(x);
};
for (int i = 0; i < 7+(1<<20); ++i) {
add_datum(i);
}
send_data(); // any leftover
}
Maintenant, std::function
c'est un peu lourd pour cela, car nous le ferions dans des environnements sans allocation sans frais généraux. Nous voudrions donc un function_view
qui n'alloue jamais.
Une autre solution est:
std::function<void(std::function<void(int result, int other_result)>)> foo(int input);
où au lieu de prendre le rappel et de l'invoquer, foo
renvoie à la place une fonction qui prend le rappel.
foo (7) ([&] (int result, int other_result) {/ * code * /}); cela rompt les paramètres de sortie des paramètres d'entrée en ayant des crochets séparés.
Avec variant
etc ++ 20coroutines, vous pouvez créer foo
un générateur d'une variante des types de retour (ou simplement du type de retour). La syntaxe n'est pas encore fixée, donc je ne donnerai pas d'exemples.
Dans le monde des signaux et des slots, une fonction qui expose un ensemble de signaux:
template<class...Args>
struct broadcaster;
broadcaster<int, int> foo();
vous permet de créer un foo
qui fonctionne asynchrone et diffuse le résultat une fois terminé.
En bas de cette ligne, nous avons une variété de techniques de pipeline, où une fonction ne fait pas quelque chose mais organise plutôt la connexion des données d'une certaine manière, et le faire est relativement indépendant.
foo( int_source )( int_dest1, int_dest2 );
alors ce code ne fait rien tant qu'il int_source
n'a pas d'entiers pour le fournir. Quand c'est le cas, int_dest1
et int_dest2
commencez à recevoir les résultats.