Існує купа способів повернення декількох параметрів. Я буду виснажливим.
Використовувати еталонні параметри:
void foo( int& result, int& other_result );
використовувати параметри вказівника:
void foo( int* result, int* other_result );
що має ту перевагу, яку вам потрібно зробити &
на сайті виклику, можливо, попереджаючи людей, що це не-параметр.
Напишіть шаблон і використовуйте його:
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)...); }
};
тоді ми можемо зробити:
void foo( out<int> result, out<int> other_result )
і все добре. foo
більше не може читати жодне значення, передане як бонус.
Інші способи визначення місця, на яке можна розмістити дані, можуть бути використані для побудови out
. Наприклад, зворотній дзвінок, щоб замінити речі де-небудь.
Ми можемо повернути структуру:
struct foo_r { int result; int other_result; };
foo_r foo();
whick працює нормально у будь-якій версії C ++ та в c ++ 17 це також дозволяє:
auto&&[result, other_result]=foo();
при нульовій вартості. Параметри навіть не можуть переміщуватися завдяки гарантованому елізію.
Ми можемо повернути std::tuple
:
std::tuple<int, int> foo();
що має зворотний бік, що параметри не названі. Це дозволяєc ++ 17:
auto&&[result, other_result]=foo();
також. Доc ++ 17 замість цього ми можемо:
int result, other_result;
std::tie(result, other_result) = foo();
що просто трохи незручніше. Але гарантований елісій тут не працює.
Зайшовши на чужу територію (і це вже після out<>
!), Ми можемо використовувати стиль продовження проходження:
void foo( std::function<void(int result, int other_result)> );
і тепер абоненти роблять:
foo( [&](int result, int other_result) {
/* code */
} );
Перевага цього стилю полягає в тому, що ви можете повернути довільну кількість значень (з рівномірним типом) без управління пам’яттю:
void get_all_values( std::function<void(int)> value )
value
зворотний виклик можна назвати 500 раз , коли ви get_all_values( [&](int value){} )
.
Для чистого божевілля ви навіть можете використовувати продовження на продовження.
void foo( std::function<void(int, std::function<void(int)>)> result );
використання яких виглядає так:
foo( [&](int result, auto&& other){ other([&](int other){
/* code */
}) });
що дозволило б мати багато стосунків між result
та other
.
Знову з однозначними значеннями ми можемо це зробити:
void foo( std::function< void(span<int>) > results )
тут ми називаємо зворотний виклик із розмахом результатів. Ми навіть можемо це робити неодноразово.
Використовуючи це, ви можете мати функцію, яка ефективно передає мегабайти даних, не роблячи жодного розподілу зі стеку.
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
це трохи важко, тому що ми це робимо в умовах нульових витрат без розподілу. Тож ми хотіли б того, function_view
що ніколи не виділяється.
Ще одне рішення:
std::function<void(std::function<void(int result, int other_result)>)> foo(int input);
де замість прийому зворотного дзвінка та виклику його, foo
замість цього повертається функція, яка приймає зворотний виклик.
foo (7) ([&] (результат int, int other_result) {/ * код * /}); це розбиває вихідні параметри з вхідних параметрів, маючи окремі дужки.
З variant
іc ++ 20У результаті, ви можете зробити foo
генератор варіанту повернення типів (або просто тип повернення). Синтаксис ще не виправлений, тому я не наведу прикладів.
У світі сигналів і слотів функція, яка виставляє набір сигналів:
template<class...Args>
struct broadcaster;
broadcaster<int, int> foo();
дозволяє створити foo
асинхронний режим, що працює, і передає результат після його завершення.
У цій лінії у нас є різноманітні методи трубопроводу, де функція щось не робить, а організовує підключення даних якимось чином, і це відносно незалежно.
foo( int_source )( int_dest1, int_dest2 );
тоді цей код нічого не робить, поки int_source
не буде надати цілі числа. Коли це станеться, int_dest1
і int_dest2
починайте отримувати результати.