现代C++之SFINAE应用(小工具编写)

2023-11-06

现代C++之SFINAE应用(小工具编写)

0.导语

现在考虑这个输入:

map<int, int> mp{
        {1, 1},
        {2, 4},
        {3, 9}};
cout << mp << endl;
vector<vector<int>> vv{
        {1, 1},
        {2, 4},
        {3, 9}};
cout << vv << endl;

输出:

{ 1 => 1, 2 => 4, 3 => 9 }
{ { 1, 1 }, { 2, 4 }, { 3, 9 } }

是不是有点像Python的print一样简单,但这背后实现也就仅仅不到100行的代码,本节来实现这种功能。

本文的代码是我修改自原作者代码,我的代码与原作者地址如下:

我修改后的代码地址:

https://github.com/Light-City/CPlusPlusThings/blob/master/tool/output/output_container.h

原作者代码的地址:

https://github.com/adah1972/output_container/blob/master/output_container.h

1.pair输出

输入:

pair<int, int> p{1, 2};
cout << p << endl;

输出:

(1, 2)

这个简单啊,直接重载<<操作符即可!

template<typename T, typename U>
std::ostream &operator<<(std::ostream &os, const std::pair<T, U> &pr) {
    os << '(' << pr.first << ", " << pr.second << ')';
    return os;
}

2.是不是pair

C++ STL容器有很多,例如:map,vector等等,我们想要针对键值对的map输出如下格式:

key => value

针对不是键值对的采用下面输出:

(a, b)

在C++ STL中针对map这种如果键值对,那么它的value_type就是个pair,因此对于上述采用哪个输出,可以采用是不是pair来判断,因此先编写下面的是不是pair检测。

// 检测是否是pair
template<typename T>
struct is_pair : std::false_type {
};
template<typename T, typename U>
struct is_pair<std::pair<T, U>> : std::true_type {
};
template<typename T>
inline constexpr bool is_pair_v = is_pair<T>::value;

首先是一个模板结构体,紧接着是模板偏特化,分别继承了false_type、true_type,而继承之后就拥有了value属性,根据C++14特性,可以对访问value进行简化:is_pair_v<T>

2.是否存在输出函数

使用SFINAE来检测是否可以直接输出:

// 检测是否可以直接输出
template<typename T>
struct has_output_function {
    template<class U>
    static auto output(U *ptr)
    -> decltype(std::declval<std::ostream &>() << *ptr,
            std::true_type());

    template<class U>
    static std::false_type output(...);

    static constexpr bool value =
            decltype(output<T>(nullptr))::value;
};

这里再提一下,当容器不能直接输出的时候,也就是第一个函数在std::declval<std::ostream &>() << *ptr会出错,但是在真正报错之前会去检测是否有重载函数,发现后面还有个output函数,最后决议不报错,这便是SFINAE。若可以直接输出,那就调用系统的输出了,否则调用后面自己写的,因此后面目标变为:针对没有输出函数的容器调用自己编写的输出函数。

3.针对没有输出函数的容器处理

通过enable_if_t限定调用<<重载操作符是针对没有输出函数的容器,内部逻辑很简单,第一次只输出元素,后面就输出,与元素,也就是用,分割元素,最后就是比较重要的output_element函数。

// 针对没有输出函数的容器处理
template<typename T,
        typename = std::enable_if_t<!has_output_function_v<T>>>
auto operator<<(std::ostream &os, const T &container)
-> decltype(container.begin(), container.end(), os) {
    os << "{ ";
    if (!container.empty()) {
        bool on_first_element = true;
        for (auto elem:container) {
            if (!on_first_element) {
                os << ", ";
            } else {
                on_first_element = false;
            }
            output_element(os, elem, container);
        }
    }
    os << " }";
    return os;
}

ouput_element函数则是完成前面提到的功能,针对容器是关联式容器,按照=>格式输出,否则按照(,)输出。

下面原理还是SFINAE来实现的,当不是pair的时候就调用第二个重载函数了,否则就是第一个。

template<typename T, typename Cont>
auto output_element(std::ostream &os, const T &element,
                    const Cont &)
-> typename std::enable_if<is_pair<typename Cont::value_type>::value, bool>::type {
    int ftype = ischarOrString(element.first);
    int stype = ischarOrString(element.second);

    output(element.first, ftype, os);
    os << " => ";
    output(element.second, stype, os);
    return true;
}

template<typename T, typename Cont>
auto output_element(std::ostream &os, const T &element,
                    const Cont &)
-> typename std::enable_if<!is_pair<typename Cont::value_type>::value, bool>::type {
    int etype = ischarOrString(element);
    output(element, etype, os);
    return false;
}

除此之外,原作者使用了标签分发也实现了这样的功能,它的如下:

template<typename T, typename Cont>
auto output_element(std::ostream& os, const T& element,
                    const Cont&, const std::true_type)
-> decltype(std::declval<typename Cont::key_type>(), os)
{
    os << element.first << " => " << element.second;
    return os;
}

template <typename T, typename Cont>
auto output_element(std::ostream& os, const T& element,
                    const Cont&, ...)
-> decltype(os)
{
    os << element;
    return os;
}

调用处则需要如下:

using element_type = decay_t<decltype(elem)>;
output_element(os,elem,container,is_pair<element_type>())

4.测试

#include <iostream>
#include <map>
#include <set>
#include <vector>
#include "output_container.h"

int main() {
    map<int, int> mp{
            {1, 1},
            {2, 4},
            {3, 9}};
    cout << mp << endl;
    vector<vector<int>> vv{
            {1, 1},
            {2, 4},
            {3, 9}};
    cout << vv << endl;

    pair<int, int> p{1, 2};
    cout << p << endl;

    set<int> s{1, 2, 3};
    cout << s << endl;

    vector<char> v{'a', 'b'};
    cout << v << endl;
    set<char *> vs{"a", "b"};
    cout << vs << endl;

    map<int, char *> mm{
            {1,   "23"},
            {2, "234hi"}
    };
    cout << mm << endl;

}

输出:

{ 1 => 1, 2 => 4, 3 => 9 }
{ { 1, 1 }, { 2, 4 }, { 3, 9 } }
(1, 2)
{ 1, 2, 3 }
{ 'a', 'b' }
{ "a", "b" }
{ 1 => "23", 2 => "234hi" }

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

现代C++之SFINAE应用(小工具编写) 的相关文章

随机推荐

  • 记一次晚上帮朋友处理文档名称的问题---正则表达式

    通过你的问题让我虎骨了一下正则表达式 因为最近工作很少用到这个知识点 所以记录一下 这个知识在文档处理有很重要的作用 而且功能也特别强大 问题 批处理文档的文件名 处理成要求的格式 用到的技术 正则表达式
  • 前端学习之Ajax

    文章目录 一 客户端与服务器 1 概念 二 URL地址 1 概念 2 URL地址的组成部分 三 分析网页的打开过程 1 理解客户端和服务器的通信过程 2 基于浏览器的开发者工具分析通信过程 四 服务器对外提供了哪些资源 1 网页常见的资源
  • GBase8s BYTE 和 TEXT 数据类型

    GBase 8s BYTE 和 TEXT 数据类型 以及如何以 JDBC API 来操纵这些数据 类型的列 BYTE 数据类型是在不可分的字节流中存储任何数据的简单大对象数据类型 此二进制数 据的示例包括电子表格 数字化的语音模式以及视频片
  • vue中使用事件监听addEventListener()来实现回车登录功能

    在开发中需要做一个点击回车按钮来实现登录的功能 使用到事件监听 事件监听addEventListener 用法 参照文献 菜鸟教程 addEventListener event function useCapture 参数一event 必须
  • Vue基础精讲 —— 规范代码三步走?关于eslint和editorconfig以及precommit的安装和使用

    eslint ESLint 是一个语法规则和代码风格的检查工具 可以用来保证写出语法正确 风格统一的代码 ESLint最初是由Nicholas C Zakas 于2013年6月创建的开源项目 它的目标是提供一个插件化的 javascript
  • 吴恩达深度学习作业_吴恩达深度学习第一部分第二周作业打卡

    1 What does a neuron compute A A neuron computes the mean of all features before applying the output to an activation fu
  • keil不能进调试模式的解决方法

    一 问题现象 平时用烧录器调试程序一直都没问题 突然今天不能调试了 故障提示如下 二 问题分析 尝试过各种解决方法 插拔烧录器 更换烧录器 插拔stm32主机电源 重启keil开发环境 问题依旧 看来问题不应该在主机和烧录器上 最后问题定位
  • spec文件

    spec文件 spec文件是配置规范文件 是RPM软件包编译过程的核心 它说明了软件包如何被配置 打那些补丁 安装哪些文件 安装到哪里 安装过程需要哪些系统级别活动 标签说明 依赖关系 Dependencies BuildRequires
  • 线上项目路由跳转报错 Loading chunk failed

    场景 线上PC端项目点击左侧路由菜单栏进行页面跳转时点击无反应并报错 Loading chunk failed 原因 经查阅资料找出原因 项目使用了路由懒加载 路由懒加载的情况下 访问当前应用进行路由跳转时都是实时动态的从服务器上拉取相应模
  • Python 递归函数返回值为 None 的解决办法

    在使用 Python 开发的过程中 避免不了会用到递归函数 但递归函数的返回值有时会出现意想不到的情况 下面来举一个例子 gt gt gt def fun i i 1 if i lt 5 fun i else return i gt gt
  • 【转载】做好功能测试,这8项必备技能了解一下!

    原文链接 功能测试是测试工程师的基础功 很多人功能测试还做不好 就想去做性能测试 自动化测试 很多人对功能测试的理解就是点点点 如何自己不用心去悟 去研究 那么你的职业生涯也就停留在点点点上了 在这里 我把我对功能测试的理解写下来 那么 功
  • Can not load Open Client,please verify that libct.dll and libcs.dll are in your

    Can not load Open Client please verify that libct dll and libcs dll are in your path Please make sure your version of Op
  • C++ 函数模板与类模板template,以及具体化、实例化

    函数模板 需要创建针对不同参数类型的实现相同功能的不同函数 注 模板不能缩短可执行程序 最终仍是有多个独立的函数定义 另 若对不同类型的参数执行不同的算法 可以重载模板定义 前提是两函数的特征标不同 例 template
  • arduino+oled显示字

    OLED 显示屏有四个引脚 分别是 SDA 数据线 SCK 时钟线 VDD 3 3V GND 在UNO开发板上I2C接口 SDA对应D4 SCK对应D5 在MEGA2560开发板上I2C接口 SDA对应D20 SCL对应D21 首先下载一个
  • 随笔 笔记

    一 ES6数组去重结果 new set array 二 cmd 管理员身份运行 ipconfig flushdns 刷新dns 有时某个网站进不去就刷新一下 比如echarts中文官网 三 检查元素中 快速搜索文件所在位置 浏览器打开 f1
  • 顶尖程序员不同于常人的 5 个区别

    2019独角兽企业重金招聘Python工程师标准 gt gt gt The Effective Engineer 的作者在写书的过程中 为了了解那些顶级程序员和普通程序员的区别 采访了很多硅谷顶级科技公司的顶尖软件工程师 他发现这些给世界带
  • Rust 移动零

    给定一个数组 nums 编写一个函数将所有 0 移动到数组的末尾 同时保持非零元素的相对顺序 请注意 必须在不复制数组的情况下原地对数组进行操作 力扣https leetcode cn problems move zeroes Rust代码
  • Java事件处理和事件派发机制

    事件处理 GUI程序是事件驱动程序 因此我们需要学习Java的事件处理 常见的事件包括 移动鼠标 单双击鼠标各个按钮 单击按钮 在文本字段输入 Swing通过事件对象来包装事件 程序可以通过事件获取事件的有关信息 事件处理的几个要素 事件源
  • python中16mod7_Python小白学习之路(十六)—【内置函数一】

    将68个内置函数按照其功能分为了10类 分别是 数学运算 7个 abs divmod max min pow round sum 类型转换 24个 bool int float complex str bytearray bytes mem
  • 现代C++之SFINAE应用(小工具编写)

    现代C 之SFINAE应用 小工具编写 0 导语 现在考虑这个输入 map