C++笔记:std::invoke

定义

cpp 复制代码
template< class F, class... Args >
std::invoke_result_t<F, Args...>
invoke( F&& f, Args&&... args ) noexcept();

参数

|------|---|-----------------------|
| f | - | 要调用的可调用 (Callable) 对象 |
| args | - | 传递给 f 的参数 |

返回值

f 返回的值。

示例

cpp 复制代码
#include <functional>
#include <iostream>
#include <type_traits>
 
struct Foo {
    Foo(int num) : num_(num) {}
    void print_add(int i) const { std::cout << num_+i << '\n'; }
    int num_;
};
 
void print_num(int i)
{
    std::cout << i << '\n';
}
 
struct PrintNum {
    void operator()(int i) const
    {
        std::cout << i << '\n';
    }
};
 
int main()
{
    // 调用自由函数
    std::invoke(print_num, -9);
 
    // 调用 lambda
    std::invoke([]() { print_num(42); });
 
    // 调用成员函数
    const Foo foo(314159);
    std::invoke(&Foo::print_add, foo, 1);
 
    // 调用(访问)数据成员
    std::cout << "num_: " << std::invoke(&Foo::num_, foo) << '\n';
 
    // 调用函数对象
    std::invoke(PrintNum(), 18);
 
}

为什么需要它?

如果没有 std::invoke,针对不同的类型,你必须编写不同的调用代码。请看下表的对比:

可调用类型 传统调用语法 使用 std::invoke
普通函数 / Lambda func(args...) std::invoke(func, args...)
成员函数指针 (obj.*ptr)(args...) std::invoke(ptr, obj, args...)
成员变量指针 obj.*ptr std::invoke(ptr, obj)

std::invoke_result_t

std::invoke_result_t和std::invoke_result是与 std::invoke 配套使用的类型萃取(Type Traits)工具,同样在 C++17 中引入。它们的主要作用是在编译期预测:如果我用某些参数调用某个函数,返回值的类型会是什么。

cpp 复制代码
// 1. 基础模板类
template< class F, class... ArgTypes >
struct invoke_result;

// 2. 类型别名(最常用)
template< class F, class... ArgTypes >
using invoke_result_t = typename invoke_result<F, ArgTypes...>::type;
  • F: 可调用对象的类型。

  • ArgTypes...: 拟传递给该对象的参数类型列表。

  • std::invoke_result<F, Args...> : 是一个结构体。你需要通过 ::type 来获取最终的类型。它还继承了 std::type_identity,如果调用是不合法的(例如参数不匹配),则不会定义 type(这在 SFINAE 中非常有用)。

  • std::invoke_result_t<F, Args...> : 是 C++17 提供的模板别名,直接返回类型。它是 typename std::invoke_result<F, Args...>::type 的简写,用起来更清爽。

可以用它和invoke处理泛型函数传输功能

cpp 复制代码
#include <iostream>
#include <type_traits>
#include <string>

int func(double x) { return static_cast<int>(x); }

template <typename F, typename... Args>
auto wrapper(F&& f, Args&&... args) 
    -> std::invoke_result_t<F, Args...>{ // 明确指定返回类型
    // 使用 invoke_result_t 自动推导返回值类型
    using ReturnType = std::invoke_result_t<F, Args...>;
    
    std::cout << "Return type is: " << typeid(ReturnType).name() << std::endl;
    
    // 执行调用
    ReturnType result = std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
    std::cout << "Result: " << result << std::endl;
    return result;
}

int main() {
    // 推导 func(double) 的返回类型,应该是 int
    wrapper(func, 3.14); 
    
    // 也可以用于 Lambda
    auto my_lambda = [](int a) -> std::string { return std::to_string(a); };
    wrapper(my_lambda, 100); // ReturnType 将被推导为 std::string
}
为什么这种写法更好?
  1. 一致性 :它与 std::invoke 的逻辑完全匹配。只要 std::invoke 能跑通的代码,这个返回类型推导就绝对不会出错。

  2. SFINAE 友好 :如果用户传入了错误的参数(比如参数个数不对),std::invoke_result_t 会导致模板匹配失败(替换失败),从而产生更干净的编译错误信息,而不是在函数体内部报错。

  3. 语义清晰 :它直接告诉读者:"这个函数的返回类型,就是调用 f 的结果类型"。

相关推荐
星恒随风3 分钟前
Linux进程信号(一):从Ctrl+C到kill、raise、alarm,深入理解信号的概念与产生原理
linux·c语言·笔记·学习·状态模式
hetao173383724 分钟前
联盟场-提高组-R2 & ZYZSC-S-Round 5
c++·算法
卿卿的产品经理日记27 分钟前
【AI产品经理实战】Day 34|计算机科学速成②:从水流开关到 Unicode,把整个电脑拆成一长串 0 和 1
人工智能·笔记·科技·学习·产品经理
X北辰北42 分钟前
C++ 进阶第二阶段总结|STL容器全家桶、迭代器失效、STL算法、序列化入门(工程必备)
开发语言·c++·算法
知产xiao_xin1 小时前
商标权的保护
经验分享·笔记·知识产权
星恒随风1 小时前
Linux进程信号(二):深入理解信号阻塞、未决、sigaction、中断机制、可重入函数与SIGCHLD
linux·运维·服务器·笔记·学习
旖旎夜光1 小时前
【LangGraph实战】LangGraph 学习笔记(五):信息裁剪、人机交互与时间旅行
笔记·python·学习·ai·人机交互·langgraph
Shadow(⊙o⊙)1 小时前
复习手写笔记C++继承相关1.0转录
c++
Shadow(⊙o⊙)1 小时前
复习手写笔记C++ 析构函数this指针虚表 转录
c++
蹊黎4 小时前
C++中vector类的部分简单实现
开发语言·c++·visual studio