You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何完美转发函数返回值并返回第二个值?基准测试函数实现疑问

解决基准测试函数的完美转发与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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 18:57:53