深入探究 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)就是指将图象中灰度差增大的方法,一次来增强物体的轮廓与边缘。因为发…...
【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...
C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...
多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...
企业如何增强终端安全?
在数字化转型加速的今天,企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机,到工厂里的物联网设备、智能传感器,这些终端构成了企业与外部世界连接的 “神经末梢”。然而,随着远程办公的常态化和设备接入的爆炸式…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...
