深入探究 C++17 std::is_invocable

news2025/2/11 9:25:09

在这里插入图片描述

文章目录

    • 一、引言
    • 二、`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 是否可以用 intdouble 类型的参数调用。由于 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_tdecltype 来检测对可调用对象 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 调用并返回 int
    std::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 调用并返回 double
    std::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_invocablestd::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 类型的参数,那么传入 shortchar 类型的参数也会被认为是可调用的,因为存在隐式转换。
  • 成员函数指针:在使用成员函数指针时,需要注意传递合适的对象实例作为第一个参数。例如,对于一个成员函数 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 也发挥着重要的作用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2295358.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenCV:图像修复

目录 简述 1. 原理说明 1.1 Navier-Stokes方法&#xff08;INPAINT_NS&#xff09; 1.2 快速行进方法&#xff08;INPAINT_TELEA&#xff09; 2. 实现步骤 2.1 输入图像和掩膜&#xff08;Mask&#xff09; 2.2 调用cv2.inpaint()函数 2.3 完整代码示例 2.4 运行结果 …

【项目日记(四)】thread cache 层

前言 前面我们对整个项目的框架进行了介绍&#xff0c;本期开始我们将进行第一层线程缓存层(thread cache)的详细介绍与实现。 目录 前言 一、thread cache 的整体设计 二、内存对齐规则和哈希映射关系 2.1 如何对齐&#xff1f; 2.2 这样设计对齐规则的好处&#xff1f…

人工智能图像分割之Mask2former源码解读

环境搭建: (1)首先本代码是下载的mmdetection-2022.9的,所以它的版本要配置好,本源码配置例如mmcv1.7,python3.7,pytorch1.13,cuda11.7。pytorch与python,cuda版本匹配可参考&#xff1a;https://www.jb51.net/python/3308342lx.htm。 (2)还有一个是先要安装一个vs2022版本或…

uniapp 编译生成鸿蒙正式app步骤

1&#xff0c;在最新版本DevEco-Studio工具新建一个空项目并生成p12和csr文件&#xff08;构建-生成私钥和证书请求文件&#xff09; 2&#xff0c;华为开发者平台 根据上面生成的csr文件新增cer和p7b文件&#xff0c;分发布和测试 3&#xff0c;在最新版本DevEco-Studio工具 文…

2024最新版Java面试题及答案,【来自于各大厂】

发现网上很多Java面试题都没有答案&#xff0c;所以花了很长时间搜集整理出来了这套Java面试题大全~ 篇幅限制就只能给大家展示小册部分内容了&#xff0c;需要完整版的及Java面试宝典小伙伴点赞转发&#xff0c;关注我后在【翻到最下方&#xff0c;文尾点击名片】即可免费获取…

Excel 融合 deepseek

效果展示 代码实现 Function QhBaiDuYunAIReq(question, _Optional Authorization "Bearer ", _Optional Qhurl "https://qianfan.baidubce.com/v2/chat/completions")Dim XMLHTTP As ObjectDim url As Stringurl Qhurl 这里替换为你实际的URLDim postD…

21.2.6 字体和边框

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 通过设置Rang.Font对象的几个成员就可以修改字体&#xff0c;设置Range.Borders就可以修改边框样式。 【例 21.6】【项目&#xff…

OpenFeign远程调用返回的是List<T>类型的数据

在使用 OpenFeign 进行远程调用时&#xff0c;如果接口返回的是 List 类型的数据&#xff0c;可以通过以下方式处理&#xff1a; 直接定义返回类型为List Feign 默认支持 JSON 序列化/反序列化&#xff0c;如果服务端返回的是 List的JSON格式数据&#xff0c;可以直接在 Feig…

三维模拟-机械臂自翻车

机械仿真 前言效果图后续 前言 最近在研究Unity机械仿真&#xff0c;用Unity实现其运动学仿真展示的功能&#xff0c;发现一个好用的插件“MGS-Machinery-master”&#xff0c;完美的解决了Unity关节定义缺少液压缸伸缩关节功能&#xff0c;内置了多个场景&#xff0c;讲真的&…

网络安全治理架构图 网络安全管理架构

网站安全攻防战 XSS攻击 防御手段&#xff1a; - 消毒。 因为恶意脚本中有一些特殊字符&#xff0c;可以通过转义的方式来进行防范 - HttpOnly 对cookie添加httpOnly属性则脚本不能修改cookie。就能防止恶意脚本篡改cookie 注入攻击 SQL注入攻击需要攻击者对数据库结构有所…

调用deepseek的API接口使用,对话,json化,产品化

背景 最近没咋用chatgpt了&#xff0c;deepseek-r1推理模型写代码质量是很高。deepseek其输出内容的质量和效果在国产的模型里面来说确实算是最强的&#xff0c;并且成本低&#xff0c;它的API接口生态也做的非常好&#xff0c;和OpenAI完美兼容。所以我们这一期来学一下怎么调…

DeepSeek大模型本地部署实战

1. 下载并安装Ollama 打开浏览器&#xff1a;使用你常用的浏览器&#xff08;如Chrome、Firefox等&#xff09;访问Ollama的官方网站。无需特殊网络环境&#xff0c;直接搜索“Ollama”即可找到。 登录与下载&#xff1a;进入Ollama官网后&#xff0c;点击右上角的“Download…

Spring Boot Actuator使用

说明&#xff1a;本文介绍Spring Boot Actuator的使用&#xff0c;关于Spring Boot Actuator介绍&#xff0c;下面这篇博客写得很好&#xff0c;珠玉在前&#xff0c;我就不多介绍了。 Spring Boot Actuator 简单使用 项目里引入下面这个依赖 <!--Spring Boot Actuator依…

[css] 黑白主题切换

link动态引入 类名切换 css滤镜 var 类名切换 v-bind css预处理器mixin类名切换 【前端知识分享】CSS主题切换方案

阿里云专有云网络架构学习

阿里云专有云网络架构 叶脊&#xff08;spine-leaf&#xff09;网络和传统三层网络拓扑对比 阿里云网络架构V3拓扑角色介绍推荐设备设备组网举例带外管理网络带外网和带内网对比设备介绍 安全网络设备介绍 参考 后续更新流量分析叶脊&#xff08;spine-leaf&#xff09;网络和传…

【AIGC】冷启动数据与多阶段训练在 DeepSeek 中的作用

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;冷启动数据的作用冷启动数据设计 &#x1f4af;多阶段训练的作用阶段 1&#xff1a;冷启动微调阶段 2&#xff1a;推理导向强化学习&#xff08;RL&#xff0…

GenAI + 电商:从单张图片生成可动态模拟的3D服装

在当今数字化时代,电子商务和虚拟现实技术的结合正在改变人们的购物体验。特别是在服装行业,消费者越来越期待能够通过虚拟试衣来预览衣服的效果,而无需实际穿戴。Dress-1-to-3 技术框架正是为此而生,它利用生成式AI模型(GenAI)和物理模拟技术,将一张普通的穿衣照片转化…

harmonyOS生命周期详述

harmonyOS的生命周期分为app(应用)的生命周期和页面的生命周期函数两部分 应用的生命周期-app应用 在app.js中写逻辑,具体有哪些生命周期函数呢,请看下图: onCreated()、onShow()、onHide()、onDestroy()这五部分 页面及组件生命周期 着重说下onShow和onHide,分别代表是不是…

记一次调整磁盘分区大小的经验

背景 redhat 6 系统 根目录挂载的逻辑卷满了&#xff0c;系统都不能正常运行了 但是/home目录挂载的另外一个逻辑卷却占用只有4% 所以想把/home挂的逻辑卷分一部分给/ 挂的逻辑卷 备份 先把系统整盘备份一下&#xff0c;用clonezilla做一个磁盘镜像&#xff0c;免得失误了搞…

软件测试就业

文章目录 2.6 初识一、软件测试理论二、软件的生产过程三、软件测试概述四、软件测试目的五、软件开发与软件测试的区别&#xff1f;六、学习内容 2.7 理解一、软件测试的定义二、软件测试的生命周期三、软件测试的原则四、软件测试分类五、软件的开发与测试模型1.软件开发模型…