如何完美转发函数返回值并返回第二个值?基准测试函数实现疑问
解决基准测试函数的完美转发与void返回处理问题
你的思路方向是对的,但确实需要解决两个核心问题:如何在tuple中正确完美转发返回值,以及处理可调用对象返回void的情况。下面一步步拆解解决方案:
1. 在Tuple中正确完美转发返回值
你猜想的std::forward<decltype(result)>(result)是完全必要的,原因如下:
decltype(auto)会完整保留std::invoke返回值的值类别:如果可调用对象返回右值(比如临时对象),result就是右值;如果返回左值引用,result就是左值引用。std::forward会根据result的类型,将其以正确的值类别传递给tuple构造函数:- 对于右值,会触发tuple的移动构造,避免不必要的拷贝;
- 对于左值引用,tuple会存储引用类型(注意:引用的生命周期需要由调用者保证,比如不要返回函数内部临时对象的引用)。
另外,用std::make_tuple代替显式构造tuple会更简洁,它能自动推导包含引用的类型,无需手动指定模板参数。
2. 处理void返回的情况
由于std::tuple无法包含void类型,我们需要在编译时分支处理两种场景:返回值为void,以及返回值非void。C++17及以上的if constexpr是实现这一点的最佳方式,它会在编译时剔除无效分支,避免语法错误。
完整实现代码
#include <chrono> #include <functional> #include <tuple> #include <type_traits> // 选择稳定的高精度时钟 using bench_clock = std::conditional_t< std::chrono::high_resolution_clock::is_steady, std::chrono::high_resolution_clock, std::chrono::steady_clock>; template <typename Func, typename... Args> auto benchmark(Func&& func, Args&&... args) { const auto begin = bench_clock::now(); // 推导可调用对象的返回类型 using ResultType = std::invoke_result_t<Func, Args...>; if constexpr (std::is_void_v<ResultType>) { // 处理void返回:执行函数后直接返回耗时 std::invoke(std::forward<Func>(func), std::forward<Args>(args)...); const auto duration = bench_clock::now() - begin; return duration; } else { // 处理非void返回:完美转发结果到tuple,同时返回耗时 decltype(auto) result = std::invoke( std::forward<Func>(func), std::forward<Args>(args)...); const auto duration = bench_clock::now() - begin; return std::make_tuple( std::forward<decltype(result)>(result), duration); } }
使用示例
#include <iostream> // 测试用可调用对象 int add(int a, int b) { return a + b; } int& increment(int& x) { x++; return x; } void do_nothing() {} int main() { // 测试返回值为右值的情况 auto [sum, dur1] = benchmark(add, 3, 5); std::cout << "Sum: " << sum << ", Duration: " << std::chrono::nanoseconds(dur1).count() << "ns\n"; // 测试返回值为左值引用的情况 int x = 10; auto [ref, dur2] = benchmark(increment, x); std::cout << "Ref value: " << ref << ", x value: " << x << ", Duration: " << std::chrono::nanoseconds(dur2).count() << "ns\n"; // 测试void返回的情况 auto dur3 = benchmark(do_nothing); std::cout << "Void duration: " << std::chrono::nanoseconds(dur3).count() << "ns\n"; return 0; }
关键细节说明
- std::invoke的必要性:它能统一处理所有可调用对象类型,包括普通函数、成员函数、lambda、仿函数等,比直接调用
func(args...)更通用。 - 类型推导的准确性:
std::invoke_result_t会正确推导可调用对象的返回类型,包括引用;std::is_void_v则用于在编译时判断是否为void返回。 - 完美转发的完整性:无论是传递给可调用对象的参数,还是返回值的转发,都使用了
std::forward,确保值类别不会丢失。
内容的提问来源于stack exchange,提问作者janekb04
相关产品推荐
相关产品推荐

