Effective Modern C++ Item 27:重载universal references
假设有一个接收universal references的模板函数foo,定义如下:
template<typename T>
void foo(T&& t)
{
cout << "foo(T&& t)" << endl;
}
如果想对某些类型做特殊处理,写一个重载版本的foo,比如想对float类型做特殊处理,就写一个接收float类型的foo:
void foo(float n)
{
cout << "foo(float n)" << endl;
}
这样,如果我们写下 foo(1.0) 时,理论上应该输出"foo(float n)",而实际上输出结果为"foo(T&& t)"。为什么呢?因为“Functions taking universal reference are the greediest functions in C++”,也就是说universal reference的函数能准确匹配几乎所有的类型。当我们调用foo(1.0)时,1.0被推导为double类型,如果调用foo(float n),就需要做narrow conversion,所以编译器会认为foo(T&& t)为更准确的匹配,除非我们写下foo(1.f)时,才会调用foo(float)。只有在类型完全准确匹配时,才会调用重载版本,否则编译器会始终认为universal reference版本为准确匹配。
这个问题在类继承中会更为隐晦,假设有一个名为Base的class,Base有一个接收universal reference的模板构造函数,定义如下:
class Base
{
public:
template<typename T>
explicit Base(T&& t)
{
cout << "Base(T&& t)" << endl;
} Base(const Base& b)
{
cout << "Base(const Base& b)" << endl;
} Base(Base&& b)
{
cout << "Base(Base&& b)" << endl;
} Base() = default;
};
然后Derived继承Base:
class Derived : public Base
{
public:
Derived() = default; Derived(const Derived& d)
:Base(d)
{
} Derived(Derived&& d)
:Base(std::move(d))
{
}
};
这时候,如果我们写:
Derived a;
Derived b(a);
Derived c(std::move(a));
那么输出结果始终为“Base(T&& t)”,也就是在Derived的拷贝构造和移动构造中,Base的函数调用都是Base(T&& t)。因为传给Base的类型为Derived,所以编译器始终认为universal reference为准确匹配。
由于universal reference的匹配过于"strong",一般都要避免重载,否则很容易出现匹配结果和预期不一致的情况。
如果无法避免重载呢?有两种方法:
1.使用type tags
考虑之前的foo函数,我们不重载foo函数,而是编写两个重载的fooImpl,fooImpl一个接受universal reference,一个接受float,两个函数用type tag参数来区分:
template<typename T>
void fooImpl(T&& t, std::false_type)
{
cout << "fooImpl(T&& t)" << endl;
} void fooImpl(float t, std::true_type)
{
cout << "fooImpl(float t)" << endl;
}
参数的type tag表示是否为浮点类型,那么foo就可以这么调用:
template<typename T>
void foo(T&& t)
{
fooImpl(std::forward<T>(t), std::is_floating_point<std::remove_reference_t<T>>());
}
这样有了type tag,只要参数是浮点类型,都会调用float版本的fooImpl。
2. 使用enable_if约束universal reference
如果某些情况我们不想使用universal reference版本,那么可以使用enable_if把它在重载决议的候选函数中屏蔽掉(SFINAE机制)。
对于foo函数,改写为:
template<typename T, typename = std::enable_if_t<
!std::is_floating_point<
std::remove_reference_t<T>
>::value>
>
void foo(T&& t)
{
cout << "foo(T&& t)" << endl;
} void foo(float t)
{
cout << "foo(float t)" << endl;
}
这样,如果参数为浮点类型,foo(T&& t)会被屏蔽掉,就会调用float版本的foo,这就和预期结果一致。
对于之前的Base class,思路也是一样的。用enable_if改写之前的代码:
class Base
{
public:
template<typename T, typename = std::enable_if_t<
!std::is_base_of <Base, std::decay_t<T>>::value
>>
explicit Base(T&& t)
{
cout << "Base(T&& t)" << endl;
} Base(const Base& b)
{
cout << "Base(const Base& b)" << endl;
} Base(Base&& b)
{
cout << "Base(Base&& b)" << endl;
} Base() = default;
};
这样,Derived的拷贝构造和移动构造就能正确调用到Base的函数(std::decay去掉references和cv-qualifiers)。
结论:
1. 尽量避免重载universal references模板函数。
2. 如果无法避免,使用type tags或者enable_if来编写重载函数。
Effective Modern C++ Item 27:重载universal references的更多相关文章
- [Effective Modern C++] Item 1. Understand template type deduction - 了解模板类型推断
条款一 了解模板类型推断 基本情况 首先定义函数模板和函数调用的形式如下,在编译期间,编译器推断T和ParamType的类型,两者基本不相同,因为ParamType常常包含const.引用等修饰符 t ...
- [Effective Modern C++] Item 7. Distinguish between () and {} when creating objects - 辨别使用()与{}创建对象的差别
条款7 辨别使用()与{}创建对象的差别 基础知识 目前已知有如下的初始化方式: ); ; }; }; // the same as above 在以“=”初始化的过程中没有调用赋值运算,如下例所示: ...
- [Effective Modern C++] Item 6. Use the explicitly typed initializer idiom when auto deduces undesired types - 当推断意外类型时使用显式的类型初始化语句
条款6 当推断意外类型时使用显式的类型初始化语句 基础知识 当使用std::vector<bool>的时候,类型推断会出现问题: std::vector<bool> featu ...
- [Effective Modern C++] Item 5. Prefer auto to explicit type declarations - 相对显式类型声明,更倾向使用auto
条款5 相对显式类型声明,更倾向使用auto 基础知识 auto能大大方便变量的定义,可以表示仅由编译器知道的类型. template<typename It> void dwim(It ...
- [Effective Modern C++] Item 4. Know how to view deduced types - 知道如何看待推断出的类型
条款四 知道如何看待推断出的类型 基础知识 有三种方式可以知道类型推断的结果: IDE编辑器 编译器诊断 运行时输出 使用typeid()以及std::type_info::name可以获取变量的类型 ...
- [Effective Modern C++] Item 3. Understand decltype - 了解decltype
条款三 了解decltype 基础知识 提供一个变量或者表达式,decltype会返回其类型,但是返回的内容会使人感到奇怪. 以下是一些简单的推断类型: ; // decltype(i) -> ...
- [Effective Modern C++] Item 2. Understand auto type deduction - 了解auto类型推断
条款二 了解auto类型推断 基础知识 除了一处例外,auto的类型推断与template一样.存在一个直接的从template类型推断到auto类型推断的映射 三类情况下的推断如下所示: // ca ...
- Effective Modern C++ Item 37:确保std::thread在销毁时是unjoinable的
下面这段代码,如果调用func,按照C++的标准,程序会被终止(std::terminate) void func() { std::thread t([] { std::chrono::micros ...
- Effective Modern C++ 42 Specific Ways to Improve Your Use of C++11 and C++14
Item 1: Understand template type deduction. Item 2: Understand auto type deduction. Item 3: Understa ...
随机推荐
- 每一个Servlet只有一个实例,多个线程
每一个Servlet只有一个实例,多个线程: Servlet: package com.stono.servlet.synchronize; import javax.servlet.http.Htt ...
- 【开发必备】今天我们来谈谈Android NDK动态链接库(so文件)的一些见解
一.写在前面 直到现在,基本我写的每一个项目都会用到NDK动态链接库的知识,可见这个也的确十分常用.那么,今天,咱们就来谈谈它. 二.什么是ABI和so 1.发展 早起的Android系统几乎只支持A ...
- Bootstrap入门(十四)组件8:媒体对象
Bootstrap入门(十四)组件8:媒体对象 这是一个抽象的样式,用以构建不同类型的组件,这些组件都具有在文本内容的左或右侧对齐的图片(就像博客评论或 Twitter 消息等). 1.基本样式 2. ...
- MVC下form表单一次上传多种类型的图片(每种类型的图片可以上传多张)
form表单一次上传多种类型的图片(每种类型的图片可以上传多张) controller中的action方法 public ActionResult UploadImage( ) { in ...
- DotNet加密方式解析--非对称加密
新年新气象,也希望新年可以挣大钱.不管今年年底会不会跟去年一样,满怀抱负却又壮志未酬.(不过没事,我已为各位卜上一卦,卦象显示各位都能挣钱...).已经上班两天了,公司大部分人还在休假,而我早已上班, ...
- 《连载 | 物联网框架ServerSuperIO教程》- 15.数据持久化接口的使用。附:3.2发布与版本更新说明。
1.C#跨平台物联网通讯框架ServerSuperIO(SSIO)介绍 <连载 | 物联网框架ServerSuperIO教程>1.4种通讯模式机制. <连载 | 物联网框架Serve ...
- Omi教程-组件
组件 Omi框架完全基于组件体系设计,我们希望开发者可以像搭积木一样制作Web程序,一切皆是组件,组件也可以嵌套子组件形成新的组件,新的组件又可以当作子组件嵌套至任意组件形成新的组件... 简单组件 ...
- android开发过程中踩过的坑
1) 4.X下 viewgroup 不一定会向下传递requestLayout,当onlayout的速度比较慢(比如子View比较复杂之类的原因),系统会跳帧!此时子View下层的view可能就不会再 ...
- 有关HTTP的粗读
.mytitle { background: #2B6695; color: white; font-family: "微软雅黑", "宋体", "黑 ...
- 从0到1学习node(七)之express搭建简易论坛
我们需要搭建的这个简易的论坛主要的功能有:注册.登录.发布主题.回复主题.下面我们来一步步地讲解这个系统是如何实现的. 总索引: http://www.xiabingbao.com/node/2017 ...