Esistono diversi modi per restituire più parametri. Ho intenzione di essere esausto.
Usa parametri di riferimento:
void foo( int& result, int& other_result );
utilizzare i parametri del puntatore:
void foo( int* result, int* other_result );
che ha il vantaggio che devi fare a &
chiamata sul sito di chiamata, avvertendo eventualmente le persone che è un parametro fuori parametro.
Scrivi un modello e usalo:
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)...); }
};
allora possiamo fare:
void foo( out<int> result, out<int> other_result )
e tutto va bene. foo
non è più in grado di leggere alcun valore trasmesso come bonus.
Altri modi per definire un punto in cui è possibile inserire i dati possono essere utilizzati per costruire out
. Un callback per posizionare le cose da qualche parte, per esempio.
Possiamo restituire una struttura:
struct foo_r { int result; int other_result; };
foo_r foo();
whick funziona bene in ogni versione di C ++ e in c ++ 17 ciò consente anche:
auto&&[result, other_result]=foo();
a costo zero. I parametri non possono nemmeno essere spostati grazie all'elisione garantita.
Potremmo restituire un std::tuple
:
std::tuple<int, int> foo();
che ha il rovescio della medaglia che i parametri non sono nominati. Questo permette alc ++ 17:
auto&&[result, other_result]=foo();
anche. Precedente ac ++ 17 possiamo invece fare:
int result, other_result;
std::tie(result, other_result) = foo();
che è solo un po 'più imbarazzante. L'elisione garantita non funziona qui, tuttavia.
Andando in un territorio sconosciuto (e questo dopo out<>
!), Possiamo usare lo stile di passaggio di continuazione:
void foo( std::function<void(int result, int other_result)> );
e ora i chiamanti fanno:
foo( [&](int result, int other_result) {
/* code */
} );
un vantaggio di questo stile è che puoi restituire un numero arbitrario di valori (con tipo uniforme) senza dover gestire la memoria:
void get_all_values( std::function<void(int)> value )
il value
callback potrebbe essere chiamato 500 volte quando tu get_all_values( [&](int value){} )
.
Per pura follia, potresti persino usare una continuazione sulla continuazione.
void foo( std::function<void(int, std::function<void(int)>)> result );
il cui uso è simile a:
foo( [&](int result, auto&& other){ other([&](int other){
/* code */
}) });
che consentirebbe molte relazioni tra result
e other
.
Ancora una volta con valori uniforniani, possiamo farlo:
void foo( std::function< void(span<int>) > results )
qui, chiamiamo il callback con un arco di risultati. Possiamo persino farlo ripetutamente.
Usando questo, puoi avere una funzione che passa in modo efficiente megabyte di dati senza fare alcuna allocazione dallo stack.
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
}
Ora, std::function
è un po 'pesante per questo, come lo faremmo in ambienti senza allocazione a zero spese. Quindi vorremmo un function_view
che non alloca mai.
Un'altra soluzione è:
std::function<void(std::function<void(int result, int other_result)>)> foo(int input);
dove invece di prendere il callback e invocarlo, foo
restituisce invece una funzione che accetta il callback.
foo (7) ([&] (int result, int other_result) {/ * code * /}); questo interrompe i parametri di output dai parametri di input avendo parentesi separate.
Con variant
ec ++ 20coroutine, potresti creare foo
un generatore di una variante dei tipi restituiti (o solo del tipo restituito). La sintassi non è ancora stata risolta, quindi non fornirò esempi.
Nel mondo dei segnali e degli slot, una funzione che espone un insieme di segnali:
template<class...Args>
struct broadcaster;
broadcaster<int, int> foo();
consente di creare un oggetto foo
che funziona in modo asincrono e trasmette il risultato al termine.
In questa linea abbiamo una varietà di tecniche di pipeline, in cui una funzione non fa qualcosa ma piuttosto organizza la connessione dei dati in qualche modo, e il fare è relativamente indipendente.
foo( int_source )( int_dest1, int_dest2 );
quindi questo codice non fa nulla fino a quando non int_source
ha numeri interi per fornirlo. Quando lo fa, int_dest1
e int_dest2
inizia a ricevere i risultati.