Is there a preferred way to return multiple values from a C++ function? For example, imagine a function that divides two integers and returns both the quotient and the rema
There are a bunch of ways to return multiple parameters. I'm going to be exhastive.
Use reference parameters:
void foo( int& result, int& other_result );
use pointer parameters:
void foo( int* result, int* other_result );
which has the advantage that you have to do a &
at the call-site, possibly alerting people it is an out-parameter.
Write a template and use it:
template
struct out {
std::function target;
out(T* t):target([t](T&& in){ if (t) *t = std::move(in); }) {}
out(std::optional* t):target([t](T&& in){ if (t) t->emplace(std::move(in)); }) {}
out(std::aligned_storage_t* t):
target([t](T&& in){ ::new( (void*)t ) T(std::move(in)); } ) {}
template // TODO: SFINAE enable_if test
void emplace(Args&&...args) {
target( T(std::forward(args)...) );
}
template // TODO: SFINAE enable_if test
void operator=(X&&x){ emplace(std::forward(x)); }
template // TODO: SFINAE enable_if test
void operator()(Args...&&args){ emplace(std::forward(args)...); }
};
then we can do:
void foo( out result, out other_result )
and all is good. foo
is no longer able to read any value passed in as a bonus.
Other ways of defining a spot you can put data can be used to construct out
. A callback to emplace things somewhere, for example.
We can return a structure:
struct foo_r { int result; int other_result; };
foo_r foo();
whick works ok in every version of C++, and in c++17 this also permits:
auto&&[result, other_result]=foo();
at zero cost. Parameters can even not even be moved thanks to guaranteed elision.
We could return a std::tuple
:
std::tuple foo();
which has the downside that parameters are not named. This permits the c++17:
auto&&[result, other_result]=foo();
as well. Prior to c++17 we can instead do:
int result, other_result;
std::tie(result, other_result) = foo();
which is just a bit more awkward. Guaranteed elision doesn't work here, however.
Going into stranger territory (and this is after out<>
!), we can use continuation passing style:
void foo( std::function );
and now callers do:
foo( [&](int result, int other_result) {
/* code */
} );
a benefit of this style is you can return an arbitrary number of values (with uniform type) without having to manage memory:
void get_all_values( std::function value )
the value
callback could be called 500 times when you get_all_values( [&](int value){} )
.
For pure insanity, you could even use a continuation on the continuation.
void foo( std::function)> result );
whose use looks like:
foo( [&](int result, auto&& other){ other([&](int other){
/* code */
}) });
which would permit many-one relationships between result
and other
.
Again with uniforn values, we can do this:
void foo( std::function< void(span) > results )
here, we call the callback with a span of results. We can even do this repeatedly.
Using this, you can have a function that efficiently passes megabytes of data without doing any allocation off the stack.
void foo( std::function< void(span) > 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 xs) {
for (auto x:xs) add_datum(x);
};
for (int i = 0; i < 7+(1<<20); ++i) {
add_datum(i);
}
send_data(); // any leftover
}
Now, std::function
is a bit heavy for this, as we would be doing this in zero-overhead no-allocation environments. So we'd want a function_view that never allocates.
Another solution is:
std::function)> foo(int input);
where instead of taking the callback and invoking it, foo
instead returns a function which takes the callback.
foo(7)([&](int result, int other_result){ /* code */ });
this breaks the output parameters from the input parameters by having separate brackets.
With variant
and c++20 coroutines, you could make foo
a generator of a variant of the return types (or just the return type). The syntax is not yet fixed, so I won't give examples.
In the world of signals and slots, a function that exposes a set of signals:
template
struct broadcaster;
broadcaster foo();
allows you to create a foo
that does work async and broadcasts the result when it is finished.
Down this line we have a variety of pipeline techniques, where a function doesn't do something but rather arranges for data to be connected in some way, and the doing is relatively independant.
foo( int_source )( int_dest1, int_dest2 );
then this code doesn't do anything until int_source
has integers to provide it. When it does, int_dest1
and int_dest2
start recieving the results.