Существует множество способов вернуть несколько параметров. Я собираюсь быть внимательным.
Использовать справочные параметры:
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){ *t = 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>
void emplace(Args&&...args) {
target( T(std::forward<Args>(args)...) );
}
template<class X>
void operator=(X&&x){ emplace(std::forward<X>(x)); }
template<class...Args>
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
.
Опять же со значениями Uniforn, мы можем сделать это:
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 result, int other_result) {/ * code * /});
это отделяет выходные параметры от входных параметров, используя отдельные скобки.
С помощью 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
начнут получать результаты.