Istnieje wiele sposobów zwracania wielu parametrów. Będę wyczerpany.
Użyj parametrów referencyjnych:
void foo( int& result, int& other_result );
użyj parametrów wskaźnika:
void foo( int* result, int* other_result );
co ma tę zaletę, że musisz to zrobić &
witrynie wywołującej, prawdopodobnie ostrzegając ludzi, że jest to parametr sparametryzowany.
Napisz szablon i użyj go:
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)...); }
};
wtedy możemy zrobić:
void foo( out<int> result, out<int> other_result )
i wszystko jest dobrze. foo
nie jest już w stanie odczytać żadnej wartości przekazanej jako bonus.
Do budowy można wykorzystać inne sposoby definiowania miejsca, w którym można umieścić dane out
. Na przykład wywołanie zwrotne, aby umieścić gdzieś różne rzeczy.
Możemy zwrócić strukturę:
struct foo_r { int result; int other_result; };
foo_r foo();
whick działa dobrze w każdej wersji C ++ i c ++ 17 pozwala to również:
auto&&[result, other_result]=foo();
przy zerowym koszcie. Parametry nie mogą nawet zostać przeniesione dzięki gwarantowanemu elekcji.
Możemy zwrócić std::tuple
:
std::tuple<int, int> foo();
co ma tę wadę, że parametry nie są nazwane. To pozwalac ++ 17:
auto&&[result, other_result]=foo();
także. Przedc ++ 17 zamiast tego możemy zrobić:
int result, other_result;
std::tie(result, other_result) = foo();
co jest nieco bardziej niezręczne. Gwarantowane wybranie jednak tutaj nie działa.
Wchodząc na nieznane terytorium (i to jest po out<>
!), Możemy użyć stylu przekazywania kontynuacji:
void foo( std::function<void(int result, int other_result)> );
a teraz dzwoniący wykonują:
foo( [&](int result, int other_result) {
/* code */
} );
zaletą tego stylu jest to, że możesz zwrócić dowolną liczbę wartości (o jednolitym typie) bez konieczności zarządzania pamięcią:
void get_all_values( std::function<void(int)> value )
value
zwrotna można nazwać 500 chwile, kiedy get_all_values( [&](int value){} )
.
Dla czystego szaleństwa możesz nawet użyć kontynuacji kontynuacji.
void foo( std::function<void(int, std::function<void(int)>)> result );
którego użycie wygląda następująco:
foo( [&](int result, auto&& other){ other([&](int other){
/* code */
}) });
co pozwoliłoby na wiele relacji między result
i other
.
Ponownie z wartościami Uniforn możemy to zrobić:
void foo( std::function< void(span<int>) > results )
tutaj nazywamy callback z szerokim zakresem wyników. Możemy to zrobić nawet wielokrotnie.
Korzystając z tego, możesz mieć funkcję, która skutecznie przekazuje megabajty danych bez dokonywania alokacji poza stos.
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
}
Teraz std::function
jest to trochę trudne, ponieważ robilibyśmy to w środowiskach zero-narzutowych bez alokacji. Chcielibyśmy więc takiego, function_view
który nigdy nie przydziela.
Innym rozwiązaniem jest:
std::function<void(std::function<void(int result, int other_result)>)> foo(int input);
gdzie zamiast odbierać oddzwonienie i wywoływać je, foo
zamiast tego zwraca funkcję, która odbiera oddzwonienie.
foo (7) ([&] (int wynik, int other_result) {/ * kod * /}); powoduje to przerwanie parametrów wyjściowych od parametrów wejściowych poprzez posiadanie oddzielnych nawiasów.
Z variant
ic ++ 20coroutines, możesz stworzyć foo
generator wariantu typów zwracanych (lub po prostu typu zwracanych). Składnia nie jest jeszcze naprawiona, więc nie podam przykładów.
W świecie sygnałów i gniazd funkcja ujawniająca zestaw sygnałów:
template<class...Args>
struct broadcaster;
broadcaster<int, int> foo();
pozwala utworzyć foo
asynchronizujący i rozgłaszający wynik po zakończeniu.
W dalszej części tej linii mamy różne techniki potokowe, w których funkcja nie robi nic, ale raczej organizuje połączenie danych w jakiś sposób, a działanie jest względnie niezależne.
foo( int_source )( int_dest1, int_dest2 );
wtedy ten kod nic nie robi , dopóki int_source
nie zostaną podane liczby całkowite. Kiedy to zrobi int_dest1
i int_dest2
zacznij otrzymywać wyniki.