没有与参数列表匹配的 重载函数 getline 实例_函数模板的重载,偏特化,和全特化...

973df87bfc17d02f998c15c69eddeb1c.png

首先说结论:

  • 函数模板支持:overload(重载),和full specialization(全特化),但是暂不支持partial specialization(偏特化)。。

下面逐一介绍相关概念。

Overload && Signature

谈到重载就需要先介绍函数的签名(Signature),它包含如下信息:

  1. 函数的裸名(不包含所属class或者namespace的作用域修饰,或者是被实例化以得到当前函数的函数模板的名字)。
  2. 函数所属的class或者namespace的作用域信息。如果该函数有内部链接属性的话,还应包含该函数所属的编译单元的相关信息。
  3. 如果函数是一个class的成员函数,且被const,volatile,或者const volatile修饰的话,还应包含这些信息。
  4. 如果函数是一个class的成员函数,且被&或者&&修饰的话,也应包含这些信息。
  5. 函数参数的类型(如果函数是经由函数模板实例化得到的话,该类型指的是模板参数被实例化之前的类型,比如T或者T1).
  6. 如果函数是经由函数模板实例化得到,还应包含返回类型(非模板函数不包含此项内容)。
  7. 如果函数是经由函数模板实例化得到,还应包含模板参数(比如T)以及用来实例化改模板的类型。

如果两个函数有不同的函数签名,那么原则上它们就可以在程序中共存,比如下面这些模板:

template<typename T1, typename T2>   //模板1
void f1(T1, T2);
template<typename T1, typename T2>  //模板2,与模板1的函数参数类型不同
void f1(T2, T1); 

template<typename T>  // 模板3
long f2(T);
template<typename T>  // 模板4,与模板3的返回值类型不同
char f2(T);

但是可以并存,并不代表可以被正常使用,因为可能会导致重载歧义。比如如果尝试调用f2(42),那么很显然会导致歧义(使用模板3还是模板4?)。

函数模板重载的一个例子:

考虑需要交换两个类型相同的变量的值,可以按如下方式实现一个模板:

template<typename T>
void swap(T& x, T& y)
{
    T tmp(x);
    x = y;
    y = tmp;
}

但是该实现包含了三次copy操作,对于某些类型并不是那么高效。比如对于包含了所存储数据的指针以及长度信息的Array,下面这种实现的效率要高的多:

template<typename T>
void swap(Array<T>& x, Array<T>& y)
{
    swap(x.ptr, y.ptr);
    swap(x.len, y.len);
}

函数模板的partial specialization(暂不支持)

首先,偏特化并不会引入新的模板,只是对原有模板(主模板)的一次扩充。在模板查找的过程中,最开始的时候只会考虑主模板,在主模板确定后,如果发现还有偏特化的模板,并且它能够与模板参数进行更好的匹配,那么会最终选择该偏特化的版本。

这一点与重载不同,重载会引入一个新的、与被重载模板并列的模板。在选择需要被实例化的模板时,会同时考虑所有的重载模板,并从中选择最匹配的那一个。

在某些重载不能满足需求的情况下,我们可能希望能够使用偏特化,比如对于:

template <typename T, typename R> //模板1
R convert(const T & rhs)
{
    ...
}

template <typename T> //模板2  试图重载模板1,虽然可以共存,但是却不能被在同一个编译单元中使用,见上节
void convert(const T & rhs)
{
    ...
}

在已经有了模板1的情况下,如果我们试图重载出一个始终返回void的模板2的话,在使用时是会遇到错误的。此时我们可能会希望能够使用偏特化。

我们所期望的函数模板偏特化的形式可能会像下面这样:

template <typename T, typename R> //模板1
R convert(const T & rhs)
{
    ...
}

template <typename T> //模板2  模板1的偏特化,以convert后面的“显式模板参数”<T , void> 为偏特化标识
void convert<T, void>(const T & rhs)
{
    ...
}

但是在某些情况下,偏特化会导致问题,比如对于:

template<typename T>
void add (T& x, int i); // 模板1, 主模板

template<typename T1, typename T2>   //模板2, 主模板的一个重载
void add (T1 a, T2 b);

template<typename T>  // 模板3,一个偏特化版本。但是它偏特化的是那一个模板呢?
void add <T*> (T*&, int);

我们将很难分辨模板3是对模板1还是模板2进行了偏特化。因此到目前为止,函数模板的偏特化还没有得到C++标准的支持,但是由于其确实能够解决一部分实际问题,因此不排除它在将来会被纳入标准的可能。

函数模板的full specialization

全特化在语言规则上和上面的偏特化类似。小的区别是,当可以通过“参数推断”推断出用来实例化该模板的类型时,可以省略掉“显式模板参数”。比如:

template<typename T> // 模板1
int f(T)
{
    return 1;
}

template<typename T> // 模板2
int f(T*)
{
    return 1;
}

template<> // 模板3, 对模板1进行了全特化,不需要写成 template<> int f<int>(int)
int f(int)
{
    return 1;
}

template<> // 模板4  对模板2进行了全特化,不需要写成 template<> int f<int>(int*)
int f(int*)
{
    return 1;
}

另一个比较值得注意的关于全特化的问题是,在很多方面,通过全特化得到的函数和常规函数都是类似的。比如,在一个程序中,只能有一份非inline的函数模板的全特化函数存在。然而,为了保证用户使用的是全特换版本的函数,而不是可能从函数模板实例化出来的其他函数,又需要将全特化函数的声明放在模板声明的后面。因此一个常规的关于全特化的代码组织方式是:

  • 一个包含了主模板和偏特化的定义,但是只包含了全特化函数声明的头文件:
#ifndef TEMPLATE_G_HPP
#define TEMPLATE_G_HPP
// template definition should appear in header file:
template<typename T>
int g(T, T x = 42)
{
return x;
} 
// specialization declaration inhibits instantiations of the template;
// definition should not appear here to avoid multiple definition errors
template<> int g(int, int y);
#endif // TEMPLATE_G_HPP
  • 以及一个包含了全特化函数定义的cpp文件:
#include "template_g.hpp"
template<> int g(int, int y)
{
    return y/2;
}

如果非要将全特换函数放置在头文件里面,那么你需要将其声明为inline的:

#ifndef TEMPLATE_G_HPP
#define TEMPLATE_G_HPP
// template definition should appear in header file:
template<typename T>
int g(T, T x = 42)
{
return x;
} 
// specialization declaration inhibits instantiations of the template;
// 如果将全特化函数放置在头文件中,那么它必须是inline的
template<> inline int g(int, int y)
{
    return y/2;
}  
#endif // TEMPLATE_G_HPP

总结

以上内容大部分摘抄自《C++ templates》第二版(https://github.com/Walton1128/CPP-Templates-2nd--),因书中论述相对分散,特摘抄于此。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值