有很多方法可以返回多个参数。我会讲得很详尽。
使用参考参数:
void foo( int& result, int& other_result );
使用指针参数:
void foo( int* result, int* other_result );
这样做的好处是,您必须在呼叫站点做一个&,可能会提醒人们这是一个out-parameter。
写出一个out<?>模板并使用它:
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不再能够读取任何作为奖励传递进来的值。
其他定义点的方法可以把数据用构造出来。例如,将东西放置在某个地方的回调。
我们可以返回一个结构:
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 )
当你get_all_values([&](int值){})时,值回调可以被调用500次。
对于纯粹的精神错乱,你甚至可以在延续上使用延续。
void foo( std::function<void(int, std::function<void(int)>)> result );
其用途如下:
foo( [&](int result, auto&& other){ other([&](int other){
/* code */
}) });
这就允许结果和他者之间存在多一关系。
同样使用统一的值,我们可以这样做:
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开始接收结果。