前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >现代C++之SFINAE应用(小工具编写)

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

作者头像
公众号guangcity
发布2020-02-13 11:43:45
1.2K0
发布2020-02-13 11:43:45
举报
文章被收录于专栏:光城(guangcity)

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

0.导语

现在考虑这个输入:

代码语言:javascript
复制
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;

输出:

代码语言:javascript
复制
{ 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输出

输入:

代码语言:javascript
复制
pair<int, int> p{1, 2};
cout << p << endl;

输出:

代码语言:javascript
复制
(1, 2)

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

代码语言:javascript
复制
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输出如下格式:

代码语言:javascript
复制
key => value

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

代码语言:javascript
复制
(a, b)

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

代码语言:javascript
复制
// 检测是否是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来检测是否可以直接输出:

代码语言:javascript
复制
// 检测是否可以直接输出
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函数。

代码语言:javascript
复制
// 针对没有输出函数的容器处理
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的时候就调用第二个重载函数了,否则就是第一个。

代码语言:javascript
复制
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;
}

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

代码语言:javascript
复制
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;
}

调用处则需要如下:

代码语言:javascript
复制
using element_type = decay_t<decltype(elem)>;
output_element(os,elem,container,is_pair<element_type>())

4.测试

代码语言:javascript
复制
#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;

}

输出:

代码语言:javascript
复制
{ 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" }
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-01-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 光城 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 现代C++之SFINAE应用(小工具编写)
    • 0.导语
      • 1.pair输出
        • 2.是不是pair
          • 2.是否存在输出函数
            • 3.针对没有输出函数的容器处理
              • 4.测试
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档