IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> c++编译时多态(1) -> 正文阅读

[C++知识库]c++编译时多态(1)

函数重载

三个阶段: 名称查找, 模板函数处理, 重载决议

前两个阶段得到候选集, 最后一个阶段选出最合适的版本

namespace animal
{
    struct Cat{};
    void feed(Cat *foo, int);
}

struct CatLike
{
    CatLike(animal::Cat *);
};
template<typename T>
void feed(T *obj, double);
template<>
void feed(animal::Cat *obj, double d);

以下会调用哪一个版本?

animal::Cat cat;
feed(&cat, 1);

以下分析

名称查找

成员函数名称查找: 通过. ->调用时, 在对应的成员类里查找

限定名称查找: 通过::调用时进行的查找

未限定名称查找: 可以通过参数依赖查找规则查找

注意参数依赖查找规则(ADL):?除了通常的作用域外, 实参类型对应的命名空间也在查找范围内

如下, 由于参数v的类型vector<int>在std命名空间里, 所以只调用sort却成功查找到了std::sort

#include "vector"
int main()
{
    std::vector<int> v;
    sort(v.begin(),v.end());
}

考虑另一种情形std::cout<<" " , 对std::cout调用的operator<<也没有注明命名空间,

操作符重载等场景中, 此规则大大简化了调用

ADL规则仅在未限定名称查找时有效(函数名左边不能有 . -> ::), 可以手动为函数添加括号禁用ADL规则

#include "vector"
int main()
{
    std::vector<int> v;
    (sort)(v.begin(),v.end());  //错误, sort未定义
}

上面中sort未定义

经过查找后剩下的候选函数

void animal::feed(Cat *foo, int);

void feed(CatLike);

template<typename T>
void feed(T *obj, double);

模板函数处理

对以下进行实例化

template<typename T>
void feed(T *obj, double);

?得到

template<animal::Cat>
void feed(animal::Cat *obj, double);

不过, 若模板参数推导与替换失败, 则会删除该候选函数

template<typename T>
void feed(T *obj, typename T::value_type);

实例化出

template<animal::Cat>
void feed(animal::Cat *obj, animal::Cat::value_type);

其中Cat并没有value_type这个成员变量, 因此替换失败, 删除该候选集, 但此时还没有错误(SFINAE机制), 当最后候选集为空时才发生编译错误

重载决议

剩下的候选集

void animal::feed(Cat *foo, int);

void feed(CatLike);

template<animal::Cat>
void feed(animal::Cat *obj, double);

由于第二个版本参数不匹配, 最后只剩下

void animal::feed(Cat *foo, int);

template<animal::Cat>
void feed(animal::Cat *obj, double);

最后根据规则决策最佳函数

1, 类型最匹配, 转换最少的

2, 非模板函数优于模板函数

3, 若多于两个模板实例, 最具体的实例最佳

根据规则1, 调用feed(&cat,1);将使用第一个版本

而调用feed(&cat,1.0);将使用第二个版本

若候选集为

void animal::feed(Cat *foo, int);

template<animal::Cat>
void feed(animal::Cat *obj, int);

仍是调用feed(&cat,1);, 根据规则2,? 将使用第一个版本(非模板函数)

若候选模板函数为

template<typename T>
void feed(T *obj, double);
template<typename T>
void feed(T obj, double);

实例化为

template<animal::Cat>
void feed(animal::Cat *obj, double);
template<animal::Cat*>
void feed(animal::Cat *obj, double);

根据规则3, 将使用第一个版本

SFINAE与declval

之前实现过一个declval模板函数用于在非求值上下文中构造对象, 但对于void等类型没有引用, 没有void&&, 应该返回自身void. 这里通过SFINAE机制补充这种情况

(14条消息) [笔记]c++: declval_TTOR的博客-CSDN博客icon-default.png?t=M7J4https://blog.csdn.net/TOPEE362/article/details/126605788?spm=1001.2014.3001.5501

初步实现如下

template<typename T>
T declval();
template<typename T,typename U=T&&>
U declval();

若为void,第二个版本参数替换后存在非法语句void&&, 替换失败, 将使用第一个版本

一般情况下(如declval<int>() ), 两个版本都符合, 会产生歧义

为此对函数添加参数进行区分

template<typename T>
T declval_(long);

template<typename T, typename U=T &&>
U declval_(int);

这样declval_<int>(0)就会优先匹配到第二个版本(字面0为int类型, 因此int和long不能对换)

此外还可再加一层封装避免每次都多写一个参数0

完整实现如下

template<typename T>
T declval_(long);

template<typename T, typename U=T &&>
U declval_(int);


template<typename T>
struct declval_protector
{
    constexpr static bool value = false;
};

template<typename T>
auto declval_a() -> decltype(declval_<T>(0))
{
    static_assert(declval_protector<T>::value,"仅能用于sizeof/decltype等非求值上下文中");
    return declval_<T>(0);
}

空类

c++ 有些类是空的, 只包含类型成员, 非虚函数静态成员变量

有?非静态变量?或?虚函数??,??虚基类?即非空类

对于空类, 实际上大小不是0, 而是1个字节(考虑空类的数组Base base[]; 每个元素必须拥有独立的地址作为区分, 因此大小不能为0)

struct Base{};
static_assert(  sizeof(Base)==1  );

空类在作为成员变量时,由于内存对齐的原因, 可能会占用更多的内存, 完全浪费掉了

struct Base{};
struct Children
{
    Base base;
    int other;
};
struct Children2
{
    Base base;
    long long other;
};

static_assert(  sizeof(Children)==8  );
static_assert(  sizeof(Children2)==16  );

分别多需要3, 7个字节进行对齐

因此可以对空类优化

1, 通过继承空类?, 可以看到空基类完全不占内存

struct Base{};
struct Children:Base
{
    int other;
};

static_assert(  sizeof(Children)==4  );

但是继承过程中, 可能存在变量名冲突, 或者空类是final时将无法继承

2, c++20提供了属性[[no_unique_address]]来解决

struct Base{};
struct Children:Base
{
    [[no_unique_address]]Base base;
    int other;
};

static_assert(  sizeof(Children)==4  );

type_traits的实现

integral_constant

template<typename T,T v>
struct integral_constant
{
    using type=integral_constant; //省略integral_constant<T,v>
    using value_type=T;
    static constexpr T value=v;
};

integral_constant将值与对应的类型包裹, 实现值与类型的一一映射关系

如using i2=integral_constant<int,2>

i2::value 为2

i2::value_type为int

对于bool有额外版本

template<bool v>
using bool_constant=integral_constant<bool,v>;

由于bool类型只有两个值, 分别为其单独定义

using true_type =integral_constant<bool,true>;
using false_type =integral_constant<bool, false>;

实现type_traits

1,? is_xxxtype

首先, 定义基本模板类, 继承false_type, 然后对于符合要求的类型, 进行特化继承true_type

template<typename T>
struct is_floating_point_:false_type {};

template<>
struct is_floating_point_<float>:true_type {};
template<>
struct is_floating_point_<double>:true_type {};
template<>
struct is_floating_point_<long double>:true_type {};

对于is_same也是同理

template<typename T,typename U>
struct is_same:false_type {}; //基本类

template<typename T>
struct is_same<T,T>:true_type {};   //特化

2, remove_xxx

也是基本类+特化类, 其中特化类加上修饰词如const, 类型参数匹配时就会自动去掉修饰词

template<typename T>
struct remove_const
{
    using type=T;
};
template<typename T>
struct remove_const<const T>
{
    using type=T;
};

3, conditional同理

template<bool v,typename Then,typename Else>
struct conditional      //基本类
{
    using type=Then;
};

template<typename Then,typename Else>
struct conditional<false,Then,Else> //false特化
{
    using type=Then;
};

4, decay类型退化

若为数组: 退化成指针

否则

????????若为函数类型: 退化成函数指针

? ? ? ? 否则去除cv修饰符

template<typename T>
struct decay
{
    using U = remove_reference_t<T>;
    using type = conditional_t<
            is_array_v<U>,
            remove_extent_t<U> *,
            conditional_t<is_function_v<U>, add_pointer_t<U>, remove_cv_t<U>>
            >;
};

类型内省

解析数组, 获取数组类型的长度信息

template<typename T>
struct array_size;

template<typename E,size_t N>
struct array_size<E[N]>
{
    using value_type=E;
    static constexpr size_t len=N;
};

int[5]无法解析成两个参数, 因此先声明一个参数的模板类型array_size, 再利用模板特化

解析函数, 获取参数, 返回类型等信息

template<typename F>
struct function_trait;

template<typename Ret,typename ...Args>
struct function_trait<Ret(Args...)>
{
    using result_type=Ret;      //返回类型
    using args_type=tuple<Args...>;     //将参数转化为tuple
    static constexpr size_t args_num=sizeof...(Args);

    template<size_t I>
    using arg=tuple_element_t<I,args_type>; //通过索引取参数类型tuple
};
using F=void(int, float);
//function_trait<F>::arg<0>       =  int
//function_trait<F>::arg<1>       =  float
//function_trait<F>::result_type  =  void

其中同样要先声明单参数的模板类, 再对其特化

enable_if

template<bool,typename= void>
struct enable_if{};

template<typename T>
struct enable_if<true,T>
{
    using type=T;
};

如果第一个参数为true, 类型为第二个参数, 否则没有类型成员(是没有, 而不是为void)

    enable_if_t<true ,void> *a;  //a为void*类型
    enable_if_t<false,void> *a;  //错误

实现针对整数和浮点数类型进行不同的比较

template<typename T, enable_if_t<is_integral_v<T>>* =nullptr>
bool numEq(T l, T r)
{
    return l == r;
}

template<typename T, enable_if_t<is_floating_point_v<T>>* =nullptr>
bool numEq(T l, T r)
{
    return abs(l - r) < numeric_limits<T>::epsilon();
}

其中第二个参数要设置默认值, 这里添加*后赋予默认空指针( *和=号之间要有空格, 否则会认为是*=操作符出错)

当比较的类型为浮点数时, 会采用相减绝对值小于该类型的最小精度作为相等的依据

标签分发

标签常常是一个空类, 如true_type和false_type也可视作标签

根据标签分发重新实现numEq的功能

template<typename T>
bool numEqImpl(T l, T r,true_type)
{
    return l == r;
}

template<typename T, false_type>
bool numEqImpl(T l, T r)
{
    return abs(l - r) < numeric_limits<T>::epsilon();
}

template<typename T>
auto numEq(T l,T r) ->enable_if_t<is_arithmetic_v<T>,bool>
{
    return numEqImpl(l,r,is_floating_point<T>{});
}

is_arithmetic_v限制了只能是整型或者浮点型

考虑迭代器标签, 标准库为迭代器提供了几种标签

struct input_iterator_tag { }; 

struct output_iterator_tag { };

struct forward_iterator_tag : public input_iterator_tag { };

struct bidirectional_iterator_tag : public forward_iterator_tag { };

struct random_access_iterator_tag : public bidirectional_iterator_tag { };

对于random_access迭代器支持算术操作, 对于bidirectional迭代器支持双向递增递减

针对advance函数, 实现根据不同迭代器标签选择合适的算法

template<typename InputI>
void advanceImpl(InputI &iter, typename iterator_traits<InputI>::difference_type n, input_iterator_tag)
{
    for (; n > 0; n--)
        ++iter;
}

template<typename BiDirI>
void advanceImpl(BiDirI &iter, typename iterator_traits<BiDirI>::difference_type n, bidirectional_iterator_tag)
{
    if (n >= 0)
        for (; n > 0; --n)++iter;
    else
        for (; n < 0; ++n)--iter;
}

template<typename RandomI>
void advanceImpl(RandomI &iter, typename iterator_traits<RandomI>::difference_type n, random_access_iterator_tag)
{
    iter += n;
}





template<typename Iter>
void advance(Iter&iter,typename iterator_traits<Iter>::difference_type n)
{
    advanceImpl(iter,n,typename iterator_traits<Iter>::iterator_category{});
}

上面分别根据三种迭代器各自实现了算法, 其中对于random_access迭代器直接将迭代器加减距离, 而对于bidirectional迭代器针对距离为负数情况单独考虑

if constexpr

与普通的if语句相比, 会在编译时对布尔常量表达式进行评估(非常量表达式则错误), 生成对应分支的代码

用if consterxpr再次对numEq 和advance 进行重写

numEq

template<typename T>
auto numEq(T l, T r) -> enable_if_t<is_arithmetic_v<T>, bool>
{
    if constexpr(is_integral_v<T>)
    {
        return l == r;
    }
    else
    {
        return abs(l - r) < numeric_limits<T>::epsilon();
    }
}

advance

template<typename Iter>
void advance(Iter &iter, typename iterator_traits<Iter>::difference_type n)
{
    using category = typename iterator_traits<Iter>::iterator_category;
    if constexpr(is_base_of_v<random_access_iterator_tag, category>)
    {
        iter += n;
    }
    else if constexpr(is_base_of_v<bidirectional_iterator_tag, category>)
    {
        if (n >= 0)
        {
            for (; n > 0; --n)
            {
                ++iter;
            }
        }
        else
        {
            for (; n < 0; ++n)
            {
                --iter;
            }
        }
    }
    else
    {
        for (; n > 0; --n)++iter;
    }
}

其中对迭代器标签不使用直接判等, 而是用是否继承某个迭代器标签来实现, 这样可以扩展

但这样必须按random_access, bidirectional, input迭代器的顺序, 因为前者也属于后者, 若顺序相反则无法精确匹配

void_t

用于检测给定的类型是否良构, 若输入的类型良构则输出类型void

考虑一个用于判断是否有成员类型type的元函数

template<typename T, typename =void>
struct HasTypeMember : false_type
{
};

template<typename T>
struct HasTypeMember<T, void_t<typename T::type>> : true_type
{
};

其中若类型有成员类型type, 则选择第二个版本, 继承true_type, 否则选择第一个版本继承false_type

static_assert(  !HasTypeMember<int>::value  );
static_assert(  HasTypeMember<true_type>::value  );

int没有成员类型type, 而true_type有成员类型type

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-09-04 00:51:43  更:2022-09-04 00:53:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 13:43:56-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码