函数重载
三个阶段: 名称查找, 模板函数处理, 重载决议
前两个阶段得到候选集, 最后一个阶段选出最合适的版本
例
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博客https://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
|