有很多方法可以返回多个参数。我将详细说明。
使用引用参数:
void foo( int& result, int& other_result );
使用指针参数:
void foo( int* result, int* other_result );
这种方法的优点是在调用时必须使用&
,可能会提醒人们这是一个输出参数。
编写一个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>
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();
这在每个版本的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) {
} );
这种风格的好处是,您可以返回任意数量的值(具有统一类型),而无需管理内存:
void get_all_values( std::function<void(int)> value )
当你使用 get_all_values([&](int value){} )
时,value
回调可能会被调用500次。
为了纯粹的疯狂,甚至可以在 continuation 上使用 continuation。
void foo( std::function<void(int, std::function<void(int)>)> result );
使用方式如下:
foo( [&](int result, auto&& other){ other([&](int other){
}) });
这将允许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();
}
现在,对于零开销无分配环境而言,
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){ });
这种方式通过使用单独的括号来打破输出参数与输入参数之间的联系。
使用生成器:
通过使用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
开始接收结果。
struct
行移到函数体外部,并将auto
函数返回替换为result
)。 - oblitumdivide
函数的定义放入一个单独的cpp文件中,我遇到了错误error: use of ‘auto divide(int, int)’ before deduction of ‘auto’
。我该如何解决? - Adriaan