Es gibt verschiedene Möglichkeiten, mehrere Parameter zurückzugeben. Ich werde exhastiv sein.
Referenzparameter verwenden:
void foo( int& result, int& other_result );
Zeigerparameter verwenden:
void foo( int* result, int* other_result );
Dies hat den Vorteil, dass Sie &
an der Anrufstelle eine Aktion durchführen müssen, um möglicherweise die Leute darauf aufmerksam zu machen, dass es sich um einen Out-Parameter handelt.
Schreiben Sie eine Vorlage und verwenden Sie sie:
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)...); }
};
dann können wir tun:
void foo( out<int> result, out<int> other_result )
und alles ist gut. foo
kann keinen als Bonus übergebenen Wert mehr lesen.
Zum Erstellen können Sie auch andere Möglichkeiten zum Definieren eines Spots verwenden, an dem Sie Daten ablegen können out
. Ein Rückruf zum Beispiel, um Dinge irgendwo zu platzieren.
Wir können eine Struktur zurückgeben:
struct foo_r { int result; int other_result; };
foo_r foo();
whick funktioniert in jeder Version von C ++ und in c ++ 17 dies erlaubt auch:
auto&&[result, other_result]=foo();
zu null Kosten. Parameter können dank garantierter Elision nicht einmal verschoben werden.
Wir könnten ein std::tuple
:
std::tuple<int, int> foo();
Das hat den Nachteil, dass Parameter nicht benannt werden. Dies ermöglicht diec ++ 17::
auto&&[result, other_result]=foo();
auch. Vorc ++ 17 wir können stattdessen tun:
int result, other_result;
std::tie(result, other_result) = foo();
das ist nur ein bisschen umständlicher. Garantierte Elision funktioniert hier jedoch nicht.
Wenn out<>
wir fremdes Gebiet betreten (und das ist danach !), Können wir den Continuation-Passing-Stil verwenden:
void foo( std::function<void(int result, int other_result)> );
und jetzt tun Anrufer:
foo( [&](int result, int other_result) {
/* code */
} );
Ein Vorteil dieses Stils besteht darin, dass Sie eine beliebige Anzahl von Werten (mit einheitlichem Typ) zurückgeben können, ohne den Speicher verwalten zu müssen:
void get_all_values( std::function<void(int)> value )
Der value
Rückruf kann bei Ihnen 500 Mal aufgerufen werden get_all_values( [&](int value){} )
.
Für reinen Wahnsinn könnten Sie sogar eine Fortsetzung der Fortsetzung verwenden.
void foo( std::function<void(int, std::function<void(int)>)> result );
deren Verwendung sieht aus wie:
foo( [&](int result, auto&& other){ other([&](int other){
/* code */
}) });
das würde viele-eins-Beziehungen zwischen result
und ermöglichen other
.
Wieder mit unifornischen Werten können wir dies tun:
void foo( std::function< void(span<int>) > results )
Hier nennen wir den Rückruf mit einer Reihe von Ergebnissen. Wir können dies sogar wiederholt tun.
Mit dieser Funktion können Sie eine Funktion verwenden, die Megabyte an Daten effizient weiterleitet, ohne eine Zuordnung vom Stapel vorzunehmen.
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
}
std::function
Dies ist jetzt etwas schwierig, da wir dies in Umgebungen ohne Zuweisung ohne Overhead tun würden. Wir möchten also eine function_view
, die niemals zuweist.
Eine andere Lösung ist:
std::function<void(std::function<void(int result, int other_result)>)> foo(int input);
Anstatt den Rückruf anzunehmen und aufzurufen, wird foo
stattdessen eine Funktion zurückgegeben, die den Rückruf entgegennimmt.
foo (7) ([&] (int result, int other_result) {/ * code * /}); Dadurch werden die Ausgabeparameter von den Eingabeparametern getrennt, indem separate Klammern verwendet werden.
Mit variant
undc ++ 20Coroutinen, Sie könnten foo
einen Generator aus einer Variante der Rückgabetypen (oder nur dem Rückgabetyp) erstellen. Die Syntax ist noch nicht festgelegt, daher werde ich keine Beispiele nennen.
In der Welt der Signale und Slots eine Funktion, die eine Reihe von Signalen verfügbar macht:
template<class...Args>
struct broadcaster;
broadcaster<int, int> foo();
Mit foo
dieser Option können Sie eine erstellen , die asynchron funktioniert und das Ergebnis nach Abschluss sendet.
In dieser Richtung gibt es eine Vielzahl von Pipeline-Techniken, bei denen eine Funktion nichts tut, sondern dafür sorgt, dass Daten auf irgendeine Weise verbunden werden, und das Vorgehen ist relativ unabhängig.
foo( int_source )( int_dest1, int_dest2 );
dann ist dieser Code nicht tut nichts , bis int_source
ganze Zahlen , es zu bieten hat. Wenn es der Fall ist, int_dest1
und int_dest2
starten Sie die Ergebnisse recieving.