当前位置: 首页 > news >正文

C++23 std::invoke_r:调用可调用 (Callable) 对象 (P2136R3)

文章目录

    • 引言
    • 背景知识回顾
      • 可调用对象
      • C++17的std::invoke
    • std::invoke_r的诞生
      • 提案背景
      • std::invoke_r的定义
      • 参数和返回值
      • 异常说明
    • std::invoke_r的使用场景
      • 指定返回类型
      • 丢弃返回值
    • std::invoke_r与std::invoke的对比
      • 功能差异
      • 使用场景差异
    • 结论

引言

在C++的发展历程中,对于可调用对象的处理一直是一个重要的话题。从早期不同类型可调用对象调用语法的不一致,到C++17引入std::invoke提供统一的调用语法,再到C++23推出std::invoke_r,每一次的改进都在提升语言的表达能力和编程的便利性。本文将深入探讨C++23中的std::invoke_r,包括其定义、使用场景、与之前版本的对比等内容。

背景知识回顾

可调用对象

在C++的世界里,“可调用”(Callable)是一个宽泛的概念,涵盖了多种实体,它们都可以像函数一样被“调用”以执行某些操作:

  • 普通函数指针:如void(*ptr)(int);
  • Lambda表达式:如auto lambda = [](int x){ return x * 2; };
  • 函数对象(Functor):重载了operator()的类实例。
  • 指向(非静态)成员函数的指针:如int MyClass::*mem_func_ptr)(int);
  • 指向(非静态)成员变量的指针:如int MyClass::*mem_data_ptr;std::invoke可用于获取其值)
  • (静态)成员函数:可以像普通函数一样通过指针或直接调用。

C++17的std::invoke

在C++17之前,调用不同类型的可调用对象需要使用不同的语法,比如直接调用函数、使用类对象的运算符重载操作符()来调用函数对象、使用成员函数指针来调用类成员函数等等。这些调用方式虽然能用,但是不够灵活,给编写和维护代码带来了困扰。

std::invoke是C++ 17标准库中引入的一个函数模板,它的引入就是为了解决这个问题,它提供了一种统一的调用语法,无论是调用普通函数、函数指针、类成员函数指针、仿函数、std::function、类成员还是lambda表达式 ,都可以使用相同的方式进行调用。

std::invoke的语法如下:

template <typename Fn, typename... Args>
decltype(auto) invoke(Fn&& fn, Args&&... args);

它接受一个可调用对象fn和相应的参数args...,并返回调用结果。例如:

#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);     // 调用 lambdastd::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); return 0;
}

通过std::invoke,我们可以在不关心可调用对象的具体类型的情况下进行调用,提高了代码的灵活性和可读性。它尤其适用于泛型编程中需要以统一方式调用各种可调用对象的场景,例如使用函数指针或成员函数指针作为模板参数的算法或容器等。

std::invoke_r的诞生

提案背景

std::invoke在N4169中被引入时,invoke<R>从提案中被移除,当时认为这种形式是不必要的,理由是在TR1实现中,结果类型是使用result_of协议确定的,或者必须在调用端指定,而在C++11引入类型推导后,它就变得过时了。

然而,随着时间的推移,情况发生了变化:

  • 2015年,LWG 2420被应用到工作草案中,INVOKE(f, args…, void)丢弃返回值的能力得到确认和规范。
  • 2016年,本文的作者提出了LWG 2690,提议引入std::invoke<R>。N4169的作者对此问题进行了评论,确认invoke<R>的缺失主要是由于论文的修订同时发布以及INVOKE(f, args…, void)的额外特殊语义。
  • 2017年,INVOKE(f, args…, void)在P0604R0中获得了当前的拼写INVOKE<R>(f, args…)。在同一篇论文中,所有新的调用特性都有了允许指定返回类型的_r变体。
  • 2018年,std::visit<R>被添加到工作草案中,INVOKE<R>的实用性不断受到关注。

std::invoke_r的定义

std::invoke_r定义于头文件<functional>,其原型如下:

template< class R, class F, class... Args >
constexpr R
invoke_r( F&& f, Args&&... args ) noexcept(/* 见下方 */);

它通过可调用对象f,以参数args调用,如同INVOKE<R>(std::forward<F>(f), std::forward<Args>(args)...) 。此重载仅在std::is_invocable_r_v<R, F, Args...>true时参与重载决议。

参数和返回值

  • 参数
    • f:可调用对象,将被调用。
    • args:传递给f的参数。
  • 返回值f返回的值,隐式转换为R,如果R不是(可能带有cv限定的)void类型。否则无返回值。

异常说明

noexcept说明:noexcept(std::is_nothrow_invocable_r_v<R, F, Args...>)

std::invoke_r的使用场景

指定返回类型

invoke_r<R>(...)可以指定可调用对象的返回类型,这是std::invoke(...)所不具备的功能。例如:

#include <functional>
#include <iostream>auto add = [](int x, int y) { return x + y; };
auto ret = std::invoke_r<float>(add, 11, 22);
static_assert(std::is_same<decltype(ret), float>());
std::cout << ret << '\n';

在这个例子中,我们使用std::invoke_r<float>调用lambda表达式add,并将返回值转换为float类型。

丢弃返回值

在一个允许指定返回类型或完整签名的调用转发器中,将void作为返回类型可以自然地丢弃返回值,这由std::is_invocable_rstd::is_nothrow_invocable_r所暗示。例如:

#include <functional>
#include <iostream>void print_num(int i){std::cout << i << '\n';
}std::invoke_r<void>(print_num, 44);

在这个例子中,我们使用std::invoke_r<void>调用print_num函数,丢弃了函数的返回值。

std::invoke_r与std::invoke的对比

功能差异

  • std::invoke提供了统一的调用语法,无论可调用对象的类型是什么,都可以使用同一种方式进行调用,但它不能指定返回类型。
  • std::invoke_rstd::invoke的基础上,增加了指定返回类型的功能,并且可以自然地丢弃返回值。

使用场景差异

  • std::invoke适用于需要统一调用各种不同类型可调用对象的场景,而不关心返回值的具体类型。
  • std::invoke_r适用于需要指定返回类型或丢弃返回值的场景。

结论

C++23引入的std::invoke_r是对std::invoke的进一步扩展,它提供了指定返回类型和丢弃返回值的功能,使得在处理可调用对象时更加灵活。在实际开发中,我们可以根据具体的需求选择使用std::invokestd::invoke_r,以提高代码的可读性和可维护性。

相关文章:

  • 基于Qt开发的前景分析
  • 楼宇智能化四章【期末复习】
  • Windows服务器部署全攻略:Flask+Vue+MySQL跨平台项目实战(pymysql版)
  • 乐西高速大凉山1号隧道实现双幅贯通:成都到昭觉9小时变3.5小时
  • C#扩展方法与Lambda表达式基本用法
  • 大模型——使用 StarRocks 作为向量数据库
  • 2025年“深圳杯”数学建模挑战赛C题-分布式能源接入配电网的风险分析
  • 抓取工具Charles配置教程(mac电脑+ios手机)
  • 【C++11】包装器:function 和 bind
  • C++语法系列之前言
  • Github 2025-04-30 C开源项目日报 Top10
  • halcon关闭图形窗口
  • 「Unity3D」TextMeshPro使用TMP_InputField实现,输入框高度自动扩展与收缩
  • Java IO流与NIO终极指南:从基础到高级应用
  • JAVA入门-JAVA数据类型
  • 永磁同步电机控制算法--线性ADRC转速环控制器(一阶、二阶)
  • Keysight万用表使用指南及基于Python采集数据生成Excel文件
  • Python os.path.join()路径拼接异常
  • 如何解决matlab/octave画图legend图例颜色一样的问题?
  • 零基础做自动驾驶集成测试(仿真)
  • 马克思主义理论研究教学名师系列访谈|丁晓强:马克思主义学者要更关注社会现实的需要
  • “网约摩托”在部分县城上线:起步价五六元,专家建议纳入监管
  • “80后”蒋美华任辽宁阜新市副市长
  • 辽宁辽阳市白塔区一饭店火灾事故举行新闻发布会,现场为遇难者默哀
  • 成都警方:在地铁公共区域用改装设备偷拍女乘客,男子被行拘
  • 一位排球青训教练的20年时光:努力提高女排球员成才率