Existem várias maneiras de retornar vários parâmetros. Eu vou ser exhastive.
Use parâmetros de referência:
void foo( int& result, int& other_result );
use os parâmetros do ponteiro:
void foo( int* result, int* other_result );
que tem a vantagem de fazer um &
no site de chamada, possivelmente alertando as pessoas de que é um parâmetro externo.
Escreva um modelo e use-o:
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)...); }
};
então nós podemos fazer:
void foo( out<int> result, out<int> other_result )
e tudo está bem. foo
não é mais capaz de ler qualquer valor transmitido como um bônus.
Outras formas de definir um ponto que você pode colocar dados podem ser usadas para construir out
. Um retorno de chamada para substituir as coisas em algum lugar, por exemplo.
Podemos retornar uma estrutura:
struct foo_r { int result; int other_result; };
foo_r foo();
whick funciona bem em todas as versões do C ++ e no c ++ 17 isso também permite:
auto&&[result, other_result]=foo();
a custo zero. Os parâmetros nem sequer podem ser movidos graças à garantia garantida.
Poderíamos retornar um std::tuple
:
std::tuple<int, int> foo();
que tem a desvantagem de que os parâmetros não são nomeados. Isso permite que oc ++ 17:
auto&&[result, other_result]=foo();
também. Antes dec ++ 17 em vez disso, podemos fazer:
int result, other_result;
std::tie(result, other_result) = foo();
o que é um pouco mais estranho. A elisão garantida não funciona aqui, no entanto.
Indo para um território mais estranho (e isso é depois out<>
!), Podemos usar o estilo de passagem de continuação:
void foo( std::function<void(int result, int other_result)> );
e agora os chamadores fazem:
foo( [&](int result, int other_result) {
/* code */
} );
Um benefício desse estilo é que você pode retornar um número arbitrário de valores (com tipo uniforme) sem precisar gerenciar a memória:
void get_all_values( std::function<void(int)> value )
o value
retorno de chamada pode ser chamado 500 vezes quando você get_all_values( [&](int value){} )
.
Por pura insanidade, você pode até usar uma continuação na continuação.
void foo( std::function<void(int, std::function<void(int)>)> result );
cujo uso se parece com:
foo( [&](int result, auto&& other){ other([&](int other){
/* code */
}) });
o que permitiria muitos relacionamentos entre result
e other
.
Novamente com valores unificados, podemos fazer o seguinte:
void foo( std::function< void(span<int>) > results )
aqui, chamamos o retorno de chamada com um intervalo de resultados. Podemos até fazer isso repetidamente.
Usando isso, você pode ter uma função que passa eficientemente megabytes de dados sem fazer nenhuma alocação fora da pilha.
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
}
Agora, std::function
é um pouco pesado para isso, pois estaríamos fazendo isso em ambientes sem alocação sem sobrecarga. Então, queremos um function_view
que nunca aloque.
Outra solução é:
std::function<void(std::function<void(int result, int other_result)>)> foo(int input);
onde, em vez de pegar o retorno de chamada e invocá-lo, foo
retorna uma função que aceita o retorno de chamada.
foo (7) ([&] (resultado int, resultado int outro) {/ * código * /}); isso quebra os parâmetros de saída dos parâmetros de entrada por colchetes separados.
Com variant
ec ++ 20corotinas, você pode criar foo
um gerador de uma variante dos tipos de retorno (ou apenas do tipo de retorno). A sintaxe ainda não foi corrigida, portanto não darei exemplos.
No mundo dos sinais e slots, uma função que expõe um conjunto de sinais:
template<class...Args>
struct broadcaster;
broadcaster<int, int> foo();
permite que você crie um foo
que funcione de forma assíncrona e transmita o resultado quando terminar.
Abaixo dessa linha, temos uma variedade de técnicas de pipeline, nas quais uma função não faz algo, mas organiza a conexão de dados de alguma forma, e a ação é relativamente independente.
foo( int_source )( int_dest1, int_dest2 );
então esse código não faz nada até int_source
ter números inteiros para fornecê-lo. Quando isso acontecer, int_dest1
e int_dest2
comece a receber os resultados.