深入探究 C++17 std::is_invocable
文章目录
- 一、引言
- 二、`std::is_invocable` 概述
- 代码示例
- 输出结果
- 三、`std::is_invocable` 的工作原理
- 简化实现示例
- 四、`std::is_invocable` 的相关变体
- 1. `std::is_invocable_r`
- 2. `std::is_nothrow_invocable` 和 `std::is_nothrow_invocable_r`
- 五、使用场景
- 1. 模板元编程
- 2. 泛型算法
- 六、注意事项
- 七、结论
一、引言
在现代 C++ 编程中,我们经常会编写一些通用的代码,这些代码需要处理不同类型的可调用对象(如函数、函数指针、成员函数指针、lambda 表达式等)。在使用这些可调用对象之前,我们可能需要在编译时就确定它们是否可以以特定的参数列表进行调用。C++17 引入的 std::is_invocable
系列类型特征就为我们提供了这样的能力,它允许我们在编译时进行调用可行性的检查,从而增强代码的健壮性和通用性。
二、std::is_invocable
概述
std::is_invocable
是定义在 <type_traits>
头文件中的一个模板元函数。它用于在编译时检查一个可调用对象是否可以使用给定的参数类型进行调用。std::is_invocable
有多个重载形式,基本形式如下:
template< class F, class... Args >
struct is_invocable;template< class F, class... Args >
inline constexpr bool is_invocable_v = is_invocable<F, Args...>::value;
代码示例
#include <iostream>
#include <type_traits>// 普通函数
void foo(int x) {std::cout << "foo called with " << x << std::endl;
}int main() {std::cout << std::boolalpha;// 检查 foo 是否可以用 int 类型参数调用std::cout << "Is foo invocable with int? " << std::is_invocable_v<decltype(foo), int> << std::endl;// 检查 foo 是否可以用 double 类型参数调用(隐式转换可行)std::cout << "Is foo invocable with double? " << std::is_invocable_v<decltype(foo), double> << std::endl;return 0;
}
输出结果
Is foo invocable with int? true
Is foo invocable with double? true
在上述代码中,我们定义了一个普通函数 foo
,它接受一个 int
类型的参数。然后使用 std::is_invocable_v
检查 foo
是否可以用 int
和 double
类型的参数调用。由于 double
可以隐式转换为 int
,所以两种检查结果都为 true
。
三、std::is_invocable
的工作原理
std::is_invocable
的实现基于 SFINAE(Substitution Failure Is Not An Error)原则。当我们使用 std::is_invocable<F, Args...>
时,编译器会尝试在编译时构造一个对可调用对象 F
的调用,参数类型为 Args...
。如果这个调用是合法的,那么 std::is_invocable<F, Args...>::value
将为 true
;否则,它将为 false
。
简化实现示例
#include <type_traits>// 辅助模板,用于检测调用是否可行
template <typename F, typename... Args, typename = void>
struct is_invocable_helper : std::false_type {};template <typename F, typename... Args>
struct is_invocable_helper<F, Args..., std::void_t<decltype(std::declval<F>()(std::declval<Args>()...))>>: std::true_type {};// 定义 is_invocable
template <typename F, typename... Args>
struct is_invocable : is_invocable_helper<F, Args...> {};// 辅助模板,用于打印结果
template <typename F, typename... Args>
void print_is_invocable() {std::cout << "Is callable with given args? " << is_invocable<F, Args...>::value << std::endl;
}// 普通函数
void bar(int x) {}int main() {std::cout << std::boolalpha;print_is_invocable<decltype(bar), int>();return 0;
}
在这个示例中,我们定义了一个辅助模板 is_invocable_helper
,它使用 std::void_t
和 decltype
来检测对可调用对象 F
的调用是否合法。如果合法,is_invocable_helper
将继承自 std::true_type
;否则,它将继承自 std::false_type
。
四、std::is_invocable
的相关变体
1. std::is_invocable_r
std::is_invocable_r
用于检查一个可调用对象是否可以使用给定的参数类型进行调用,并且返回值可以隐式转换为指定的类型。
#include <iostream>
#include <type_traits>int add(int a, int b) {return a + b;
}int main() {std::cout << std::boolalpha;// 检查 add 是否可以用 int, int 调用并返回 intstd::cout << "Is add invocable with int, int and return int? " << std::is_invocable_r_v<int, decltype(add), int, int> << std::endl;// 检查 add 是否可以用 int, int 调用并返回 doublestd::cout << "Is add invocable with int, int and return double? " << std::is_invocable_r_v<double, decltype(add), int, int> << std::endl;return 0;
}
2. std::is_nothrow_invocable
和 std::is_nothrow_invocable_r
std::is_nothrow_invocable
检查一个可调用对象是否可以使用给定的参数类型进行调用,并且调用过程不会抛出异常。std::is_nothrow_invocable_r
则在此基础上还要求返回值可以隐式转换为指定的类型。
#include <iostream>
#include <type_traits>// 不抛出异常的函数
void safe_foo(int x) noexcept {std::cout << "safe_foo called with " << x << std::endl;
}int main() {std::cout << std::boolalpha;// 检查 safe_foo 是否可以用 int 调用且不抛出异常std::cout << "Is safe_foo nothrow invocable with int? " << std::is_nothrow_invocable_v<decltype(safe_foo), int> << std::endl;return 0;
}
五、使用场景
1. 模板元编程
在模板元编程中,我们经常需要根据可调用对象的调用可行性来选择不同的实现路径。
#include <iostream>
#include <type_traits>template <typename F, typename... Args, std::enable_if_t<std::is_invocable_v<F, Args...>, int> = 0>
auto call_if_invocable(F&& f, Args&&... args) {return std::forward<F>(f)(std::forward<Args>(args)...);
}template <typename F, typename... Args, std::enable_if_t<!std::is_invocable_v<F, Args...>, int> = 0>
void call_if_invocable(F&&, Args&&...) {std::cout << "Not invocable." << std::endl;
}void baz(int x) {std::cout << "baz called with " << x << std::endl;
}int main() {call_if_invocable(baz, 42);call_if_invocable([](double) {}, 10); // 这里不匹配调用,输出 Not invocable.return 0;
}
2. 泛型算法
在编写泛型算法时,我们可以使用 std::is_invocable
来确保传入的可调用对象符合算法的要求。
#include <iostream>
#include <vector>
#include <type_traits>template <typename Container, typename Func, std::enable_if_t<std::is_invocable_v<Func, typename Container::value_type>, int> = 0>
void apply(Container& c, Func f) {for (auto& elem : c) {f(elem);}
}int main() {std::vector<int> numbers = {1, 2, 3, 4, 5};auto print = [](int x) { std::cout << x << " "; };apply(numbers, print);std::cout << std::endl;return 0;
}
六、注意事项
- 隐式转换:
std::is_invocable
会考虑参数的隐式转换。例如,如果一个函数接受int
类型的参数,那么传入short
或char
类型的参数也会被认为是可调用的,因为存在隐式转换。 - 成员函数指针:在使用成员函数指针时,需要注意传递合适的对象实例作为第一个参数。例如,对于一个成员函数
void MyClass::func()
,调用时需要传递MyClass
的实例或指针。
#include <iostream>
#include <type_traits>class MyClass {
public:void member_func() {std::cout << "Member function called." << std::endl;}
};int main() {std::cout << std::boolalpha;// 检查成员函数指针是否可调用std::cout << "Is member_func invocable? " << std::is_invocable_v<decltype(&MyClass::member_func), MyClass&> << std::endl;return 0;
}
七、结论
std::is_invocable
系列类型特征为 C++ 程序员提供了强大的编译时检查能力,使得我们可以在编写通用代码时更加安全和高效。通过合理使用 std::is_invocable
及其变体,我们可以避免在运行时出现调用错误,提高代码的健壮性和可维护性。同时,在模板元编程和泛型算法中,std::is_invocable
也发挥着重要的作用。
相关文章:

深入探究 C++17 std::is_invocable
文章目录 一、引言二、std::is_invocable 概述代码示例输出结果 三、std::is_invocable 的工作原理简化实现示例 四、std::is_invocable 的相关变体1. std::is_invocable_r2. std::is_nothrow_invocable 和 std::is_nothrow_invocable_r 五、使用场景1. 模板元编程2. 泛型算法 …...
Vmware网络模式
一、Vmware虚拟网络 Vmware共支持创建20个虚拟网络,相当于现实生活的交换机,名称vmnet0-vmnet19 没创建一个虚拟网络。对应在物理机会自动生成相应的虚拟网卡 该虚拟网卡用于和对应的虚拟网络中的虚拟机通信 二、虚拟网络的工作模式 1、nat模式 …...

神经辐射场(NeRF):从2D图像到3D场景的革命性重建
神经辐射场(NeRF):从2D图像到3D场景的革命性重建 引言 在计算机视觉和图形学领域,如何从有限的2D图像中高效且准确地重建真实的3D场景,一直是一个重要的研究方向。传统的3D重建方法,如多视角几何、点云重建…...

深入解析AI技术原理
序言 在当今数字化时代,人工智能(AI)已经成为科技领域最炙手可热的话题之一。从智能家居到自动驾驶汽车,从医疗诊断到金融风险预测,AI的应用无处不在。然而,对于许多人来说,AI背后的技术原理仍然充满了神秘色彩。本文将深入探讨AI的核心技术原理,从基础理论到前…...
PDF 2.0 的新特性
近来闲来无事,就想着把PDF的新标准研究研究,略有所得,和大家分享一下。 PDF 2.0的主要新特性包括更高级的加密算法、改进的数字签名和权限管理机制、增强了对非罗马字符的支持,以及扩展了标签架构和3D建模语言“PRC”的支…...

Matlab机械手碰撞检测应用
本文包含三个部分: Matlab碰撞检测的实现URDF文件的制作机械手STL文件添加夹爪 一.Matlab碰撞检测的实现 首先上代码 %% 检测在结构环境中机器人是否与物体之间发生碰撞情况,如何避免? % https://www.mathworks.com/help/robotics/ug/che…...

(root) Additional property include:is not allowed
参考:执行docker compose命令出现 Additional property include is not allowed_(root) additional property include is not allowed-CSDN博客 原因是docker-compose的版本太低,下载最新的替换即可。 第一次2.6.x版本改成了2.19.x不够高,所…...
react 18父子组件通信
在React 18中,父子组件之间的通信方式与之前的版本基本相同,主要可以通过以下几种方式实现: 1. Props(属性) 父组件向子组件传递数据: 父组件通过属性(props)向子组件传递数据&am…...
FastReport 加载Load(Stream) 模板内包含换行符不能展示
如下代码 当以FastReport 载入streams时 当模板内包含换行符时会导致不能正常生成pdf System.Xml.XmlDocument newFrxXml new System.Xml.XmlDocument(); newFrxXml.Load(fileName);FastReport.Report report new FastReport.Report();using (var memStream new MemoryStre…...
Maven 中常用的 scope 类型及其解析
在 Maven 中,scope 属性用于指定依赖项的可见性及其在构建生命周期中的用途。不同的 scope 类型能够影响依赖项的编译和运行阶段。以下是 Maven 中常用的 scope 类型及其解析: compile(默认值): 这是默认的作用域。如果…...
vue3:点击子组件进行父子通信
问: 子组件怎么和爷爷组件通信 回答: 在Vue 3中,子组件和爷爷组件之间的通信可以通过事件冒泡和状态管理来实现。你可以使用Vue的事件系统来传递事件,或者使用全局状态管理库如Vuex或Pinia。以下是一个使用事件冒泡的示例&…...

Composo:企业级AI应用的质量守门员
在当今快速发展的科技世界中,人工智能(AI)的应用已渗透到各行各业。然而,随着AI技术的普及,如何确保其可靠性和一致性成为了企业面临的一大挑战。Composo作为一家致力于为企业提供精准AI评估服务的初创公司,通过无代码和API双模式,帮助企业监测大型语言模型(LLM)驱动的…...
Jackson扁平化处理对象
POJO对象 Data public class People {private PeopleInfo peopleInfo;private List<String> peopleIds;private Map<String, String> peopleMap;Datapublic static class PeopleInfo {private String name;private String address;} }JSON序列化处理 直接将对象进…...
Java即时编译器(JIT)的原理及在美团的实践经验
基本功 | Java即时编译器原理解析及实践 - 美团技术团队 这篇文章由美团AI平台/搜索与NLP部的珩智、昊天、薛超撰写,深入介绍了Java即时编译器(JIT)的原理及在美团的实践经验。 Java执行过程与即时编译器概述 Java执行过程:Java…...

使用 Ollama 在 Windows 环境部署 DeepSeek 大模型实战指南
文章目录 前言Ollama核心特性 实战步骤安装 Ollama验证安装结果部署 DeepSeek 模型拉取模型启动模型 交互体验命令行对话调用 REST API 总结个人简介 前言 近年来,大语言模型(LLM)的应用逐渐成为技术热点,而 DeepSeek 作为国产开…...
算法基础之八大排序
文章目录 概要1. 冒泡排序(Bubble Sort)2. 选择排序(Selection Sort)3. 插入排序(Insertion Sort)4. 希尔排序(Shell Sort)5. 归并排序(Merge Sort)6. 快速排…...
使用TensorFlow和Keras构建卷积神经网络:图像分类实战指南
使用TensorFlow和Keras构建卷积神经网络:图像分类实战指南 一、前言:为什么选择CNN进行图像分类? 在人工智能领域,图像分类是计算机视觉的基础任务。传统的机器学习方法需要人工设计特征提取器,而深度学习通过卷积神经…...

音频进阶学习十一——离散傅里叶级数DFS
文章目录 前言一、傅里叶级数1.定义2.周期信号序列3.表达式DFSIDFS参数含义 4.DFS公式解析1)右边解析 T T T、 f f f、 ω \omega ω的关系求和公式N的释义求和公式K的释义 e j ( − 2 π k n N ) e^{j(\frac{-2\pi kn}{N})} ej(N−2πkn)的释义 ∑ n 0 N − 1 e…...

20.<Spring图书管理系统①(登录+添加图书)>
PS:关于接口定义 接口定义,通常由服务器提供方来定义。 1.路径:自己定义 2.参数:根据需求考虑,我们这个接口功能完成需要哪些信息。 3.返回结果:考虑我们能为对方提供什么。站在对方角度考虑。 我们使用到的…...

关于图像锐化的一份介绍
在这篇文章中,我将介绍有关图像锐化有关的知识,具体包括锐化的简单介绍、一阶锐化与二阶锐化等方面内容。 一、锐化 1.1 概念 锐化(sharpening)就是指将图象中灰度差增大的方法,一次来增强物体的轮廓与边缘。因为发…...

大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...

基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...

css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...

python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...

YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...

ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...