Effective Modern C++ Item 27:重载universal r…
2018-06-17 23:05:58来源:未知 阅读 ()
假设有一个接收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来编写重载函数。
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- C++ 转换函数搭配友元函数 2020-06-10
- C++ 自动转换和强制类型转换(用户自定义类类型) 2020-06-10
- C++ rand函数 2020-06-10
- C++ 友元函数 2020-06-10
- C++ 运算符重载 2020-06-10
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash