深入探究 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 是否可以用 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 也发挥着重要的作用。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

P1049 装箱问题(dp)

#include<bits/stdc.h> using namespace std;int main() {int v,n;cin>>v>>n;int a[30];int dp[20005];for(int i0;i<n;i){cin>>a[i];}memset(dp,0,sizeof(dp));// 设置所有元素为0&#xff0c;表示最大体积为0for(int i0;i<n;i){for(int jv;j&…

Groovy基础

引言&#xff1a; Groovy 是一种基于 Java 平台的动态编程语言&#xff08;指在运行时进行类型检查的语言。在使用动态语言编写程序时&#xff0c;变量的类型不需要在声明时明确指定&#xff0c;而是在运行时根据赋给变量的值来确定类型。动态语言在代码执行过程中会进行类型检…

Flink CDC YAML:面向数据集成的 API 设计

摘要&#xff1a;本文整理自阿里云智能集团 、Flink PMC Member & Committer 徐榜江&#xff08;雪尽&#xff09;老师在 Flink Forward Asia 2024 数据集成&#xff08;一&#xff09;专场中的分享。主要分为以下四个方面&#xff1a; Flink CDC YAML API Transform A…

OpenCV:视频背景减除

目录 简述 1. MOG &#x1f537;1.1 主要特点 &#x1f537;1.2 代码示例 &#x1f537;1.3 运行效果 2. MOG2 &#x1f537;2.1 主要特点 &#x1f537;2.2 代码示例 &#x1f537;2.3 运行效果 3. KNN 4. GMG 5. CNT 6. LSBP 7. 如何选择适合的接口&#xff…

PAT乙级( 1009 说反话 1010 一元多项式求导)C语言版本超详细解析

1009 说反话 给定一句英语&#xff0c;要求你编写程序&#xff0c;将句中所有单词的顺序颠倒输出。 输入格式&#xff1a; 测试输入包含一个测试用例&#xff0c;在一行内给出总长度不超过 80的字符串。字符串由若干单词和若干空格组成&#xff0c;其中单词是由英文字母&#x…

OpenCV2D 特征框架 (19)目标检测类cv::CascadeClassifier的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::CascadeClassifier 是 OpenCV 中用于对象检测的一个核心类&#xff0c;特别适用于基于 Haar 特征和 LBP&#xff08;局部二进制模式&#xf…

大数据学习之SparkSql

95.SPARKSQL_简介 网址&#xff1a; https://spark.apache.org/sql/ Spark SQL 是 Spark 的一个模块&#xff0c;用于处理 结构化的数据 。 SparkSQL 特点 1 易整合 无缝的整合了 SQL 查询和 Spark 编程&#xff0c;随时用 SQL 或 DataFrame API 处理结构化数据。并且支…

RabbitMQ 从入门到精通:从工作模式到集群部署实战(四)

#作者&#xff1a;闫乾苓 系列前几篇&#xff1a; 《RabbitMQ 从入门到精通&#xff1a;从工作模式到集群部署实战&#xff08;一&#xff09;》&#xff1a;link 《RabbitMQ 从入门到精通&#xff1a;从工作模式到集群部署实战&#xff08;二&#xff09;》&#xff1a; lin…

ip地址是手机号地址还是手机地址

在数字化生活的浪潮中&#xff0c;IP地址、手机号和手机地址这三个概念如影随形&#xff0c;它们各自承载着网络世界的独特功能&#xff0c;却又因名称和功能的相似性而时常被混淆。尤其是“IP地址”这一术语&#xff0c;经常被错误地与手机号地址或手机地址划上等号。本文旨在…

Django开发入门 – 0.Django基本介绍

Django开发入门 – 0.Django基本介绍 A Brief Introduction to django By JacksonML 1. Django简介 1) 什么是Django? 依据其官网的一段解释&#xff1a; Django is a high-level Python web framework that encourages rapid development and clean, pragmatic design. …

深度解析DeepSeek模型系列:从轻量级到超大规模(附DeepSeek硬件配置清单)

在人工智能领域&#xff0c;深度学习模型的选择对于任务的执行效率和精度至关重要。DeepSeek模型系列提供了多种不同参数量的版本&#xff0c;以满足不同场景下的需求。本文将详细解析DeepSeek模型系列的特点、适用场景以及硬件需求。 DeepSeek模型系列概览 DeepSeek模型系列…

树和二叉树_7

树和二叉树_7 一、leetcode-102二、题解1.引库2.代码 一、leetcode-102 二叉树的层序遍历 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 样例输入&#xff1a;root [3,9,20,null,nu…

【DeepSeek】私有化本地部署图文(Win+Mac)

目录 一、DeepSeek本地部署【Windows】 1、安装Ollama 2、配置环境变量 3、下载模型 4、使用示例 a、直接访问 b、chatbox网页访问 二、DeepSeek本地部署【Mac】 1、安装Ollama 2、配置环境变量 3、下载模型 4、使用示例 5、删除已下载的模型 三、DeepSeek其他 …

<tauri><rust><GUI>基于rust和tauri,在已有的前端框架上手动集成tauri示例

前言 本文是基于rust和tauri&#xff0c;由于tauri是前、后端结合的GUI框架&#xff0c;既可以直接生成包含前端代码的文件&#xff0c;也可以在已有的前端项目上集成tauri框架&#xff0c;将前端页面化为桌面GUI。 环境配置 系统&#xff1a;windows 10 平台&#xff1a;visu…

每日学习 设计模式 五种不同的单例模式

狮子大佬原文 https://blog.csdn.net/weixin_40461281/article/details/135050977 第一种 饿汉式 为什么叫饿汉,指的是"饿" 也就是说对象实例在程序启动时就已经被创建好,不管你是否需要,它都会在类加载时立即实例化,也就是说 实例化是在类加载时候完成的,早早的吃…

从技术体系到实践案例:浪潮信息解码金融算力演进路径

作为金融科技领域的重要参与者&#xff0c;浪潮信息作为核心参编单位&#xff0c;联合中国金电、工商银行等33家机构共同完成《中国金融科技发展报告&#xff08;2024&#xff09;》&#xff08;以下简称蓝皮书&#xff09;编撰。浪潮信息凭借在数字基础设施领域的技术积累&…

题海拾贝:【高精度】减法

Hello大家好&#xff01;很高兴我们又见面啦&#xff01;给生活添点passion&#xff0c;开始今天的编程之路&#xff01; 我的博客&#xff1a;<但凡. 我的专栏&#xff1a;《编程之路》、《数据结构与算法之美》、《题海拾贝》 欢迎点赞&#xff0c;关注&#xff01; 1、题…

知识库升级新思路:用生成式AI打造智能知识助手

在当今信息爆炸的时代&#xff0c;企业和组织面临着海量数据的处理和管理挑战。知识库管理系统&#xff08;Knowledge Base Management System, KBMS&#xff09;作为一种有效的信息管理工具&#xff0c;帮助企业存储、组织和检索知识。然而&#xff0c;传统的知识库系统往往依…

设计模式-生产者消费者模型

阻塞队列&#xff1a; 在介绍生产消费者模型之前&#xff0c;我们先认识一下阻塞队列。 阻塞队列是一种支持阻塞操作的队列&#xff0c;常用于生产者消费者模型&#xff0c;它提供了线程安全的队列操作&#xff0c;并且在队列为空或满时&#xff0c;能够阻塞等待&#xff0c;…

1Panel应用推荐:WordPress开源博客软件和内容管理系统

1Panel&#xff08;github.com/1Panel-dev/1Panel&#xff09;是一款现代化、开源的Linux服务器运维管理面板&#xff0c;它致力于通过开源的方式&#xff0c;帮助用户简化建站与运维管理流程。为了方便广大用户快捷安装部署相关软件应用&#xff0c;1Panel特别开通应用商店&am…