Hay muchas formas de devolver múltiples parámetros. Voy a estar exhausto.
Usar parámetros de referencia:
void foo( int& result, int& other_result );
usar parámetros de puntero:
void foo( int* result, int* other_result );
que tiene la ventaja de que tienes que hacer un &
llamada en el sitio de la llamada, posiblemente alertando a las personas de que es un parámetro fuera de lugar.
Escribe una plantilla y úsala:
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)...); }
};
entonces podemos hacer:
void foo( out<int> result, out<int> other_result )
Y todo está bien. foo
ya no puede leer ningún valor pasado como bonificación.
Se pueden utilizar otras formas de definir un lugar en el que se pueden colocar datos out
. Una devolución de llamada para colocar cosas en algún lugar, por ejemplo.
Podemos devolver una estructura:
struct foo_r { int result; int other_result; };
foo_r foo();
whick funciona bien en todas las versiones de C ++, y en c ++ 17 esto también permite:
auto&&[result, other_result]=foo();
a costo cero Los parámetros ni siquiera se pueden mover gracias a la elisión garantizada.
Podríamos devolver un std::tuple
:
std::tuple<int, int> foo();
que tiene el inconveniente de que los parámetros no tienen nombre. Esto permite elc ++ 17:
auto&&[result, other_result]=foo();
también. Antes dec ++ 17 en su lugar podemos hacer:
int result, other_result;
std::tie(result, other_result) = foo();
lo cual es un poco más incómodo. Sin embargo, la elisión garantizada no funciona aquí.
Al entrar en territorio extraño (¡y esto es después out<>
!), Podemos usar el estilo de pase de continuación:
void foo( std::function<void(int result, int other_result)> );
y ahora las personas que llaman hacen:
foo( [&](int result, int other_result) {
/* code */
} );
Una ventaja de este estilo es que puede devolver un número arbitrario de valores (con tipo uniforme) sin tener que administrar la memoria:
void get_all_values( std::function<void(int)> value )
la value
devolución de llamada podría llamarse 500 veces cuando usted get_all_values( [&](int value){} )
.
Por pura locura, incluso podría usar una continuación en la continuación.
void foo( std::function<void(int, std::function<void(int)>)> result );
cuyo uso se parece a:
foo( [&](int result, auto&& other){ other([&](int other){
/* code */
}) });
lo que permitiría muchas relaciones entre result
y other
.
De nuevo con los valores de California, podemos hacer esto:
void foo( std::function< void(span<int>) > results )
aquí, llamamos a la devolución de llamada con una serie de resultados. Incluso podemos hacer esto repetidamente.
Con esto, puede tener una función que pase eficientemente megabytes de datos sin hacer ninguna asignación fuera de la pila.
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
}
Ahora, std::function
es un poco pesado para esto, ya que estaríamos haciendo esto en entornos de cero sobrecarga sin asignación. Así que querríamos un function_view
que nunca asigne.
Otra solución es:
std::function<void(std::function<void(int result, int other_result)>)> foo(int input);
donde en lugar de tomar la devolución de llamada e invocarla, en su foo
lugar , devuelve una función que toma la devolución de llamada.
foo (7) ([&] (int resultado, int otro_resultado) {/ * código * /}); esto rompe los parámetros de salida de los parámetros de entrada al tener corchetes separados.
Con variant
yc ++ 20corutinas, podría hacer foo
un generador de una variante de los tipos de retorno (o solo el tipo de retorno). La sintaxis aún no está arreglada, por lo que no daré ejemplos.
En el mundo de las señales y las ranuras, una función que expone un conjunto de señales:
template<class...Args>
struct broadcaster;
broadcaster<int, int> foo();
le permite crear uno foo
que funciona de forma asíncrona y difunde el resultado cuando finaliza.
En esta línea, tenemos una variedad de técnicas de canalización, donde una función no hace algo, sino que organiza los datos para que se conecten de alguna manera, y la acción es relativamente independiente.
foo( int_source )( int_dest1, int_dest2 );
entonces este código no hace nada hasta que int_source
tenga números enteros para proporcionarlo. Cuando lo haga, int_dest1
y int_dest2
comience a recibir los resultados.