• 函数模板
    • 类型推导
    • 重载函数模板


函数模板

函数模板代表了一个函数家族。

返回两个值(未定义类型)中较大的那个值:

template<typename T>
inline const T& max(const T& x, const T& y)
{
    return x > y ? x:y;
}

模板参数T表示的是,调用者使用这个函数时所指定的任意参数类型。你可以使用任意类型(基本类型、类等)来实例化这一了类型参数,只要改类型支持函数体内所使用的操作即可,如本例的operator>

因标准模板库(STL)的标准命名空间(std)也有一个std::max()模板,为避免二义性,需在我们定义的max()模板之前加上域限定符::,确保我们调用的是全局命名空间中的max()模板函数。

::max(7, 42);
::max(7.7, 8.8);
::max("mathmetics", "math");
::max(7, 7.);       
        // 错误,没有与参数列表(int, double)匹配的函数模板
        // 这里用到了函数模板的自动类型推导机制(deduction)

通常而言,并非把模板编译成一个可以处理任何类型的单一实体,,而是视函数模板模板参数的不同,实例化出不同的实体。

如果试图基于一个不支持模板函数内部所使用操作的参数类型实例化一个模板,将会导致一个编译器错误:

std::complex<float> c1, c2;
::max(c1, c2);      // 编译期错误

因此,可以得出一个结论,模板被编译了两次:
- 实例化之前,检查模板代码本身,查看基本语法是否正确
- 实例化期间,检查模板代码,查看是否所有的操做都有效。

类型推导

template<typename T>
inline const T& max(const T& x, const T& y)
{
    return x > y ? x : y;
}
::max(4, 7);        // ok
::max(4, 4.2);      // error,参数类型与函数模板的不匹配

有三种方式处理上述的错误:

  • 对其中之一强制类型转换
::max(static_cast<double>(4), 4.2);
  • 显式指定(或者叫限定)T的类型
::max<double>(4, 4.2);
  • 修改函数模板,使其适配两个不同类型的参数类型
template<typename T1, typename T2>
inline T1 max(const T1& x, const T2& y)
{
    return x > y ? x : x;
}

能够向函数模板传递两个不同类型的参数,但返回类型只有一个,就有很大的可能性涉及类型的转换。这就造成了两大缺陷:

  • 取决于调用实参的顺序,42和66.6的最大值可以是66.6也可能是66

  • 类型转换将导致局部临时对象的创建,无法通过引用返回结果。

可能的解决方案:

template<typename T1, typename T2, typename RT>
inline RT max(const T1& x, const T2& y)
{
    return x > y ? x : y;
}

这时的的客户端程序(类型推导不适合于模板实参):

::max<int, double, double>(4, 4.2);

稍显啰嗦,纵然是这种方式,我们仍可结合类型推导简化其形式:

template<typename RT, typename T1, typename T2>
inline RT max(const T1& x, const T2& y)
{
    return x > y ? x:y;
}

客户端代码:

::max<double>(4, 4.2);

重载函数模板

const int& max(const int& x, const int& y)
{
    return x > y ? x : y;
}

template<typename T>
inline const T& max(const T& x, const T& y)
{
    return x > y ? x : y;
}

template<typename T>
inline const T& max(const T& x, const T& y, const T& z)
{
    return ::max(::max(x, y), z)
}

如上述代码所示,一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。对于非模板函数和同名的函数模板,如果其他条件都相同的话,在调用的时候,会优先调用非模板函数。

::max(7, 42);           // 匹配非模板函数
::max(7., 42.);         // ::max<double>,类型推导
::max('a', 'b');        // ::max<char>, 类型推导

还可以显示地指定一个空的模板实参列表,这个语法似乎告诉编译器,只有模板才能匹配这个调用,

::max<>(7, 42);     // max<int>

模板不允许自动类型转换,但普通函数可以进行自动类型转换

::max('a', 42.7);    
        // 可以通过,通过调试追踪可以发现,调用的是非模板函数

在所有重载的实现中,一般都是通过引用来传递每个实参的。一个好的设计是在,重载函数模板的时候,最好只是改变需要改变的内容。你应该把改变限制在:改变参数的数目,或者显示地指定模板参数,否则将出现非预期的结果。

template<typename T>
inline const T& max(const T& x, const T& y)
{
    return x > y ? x : y;
}   

// 通过传地址进行调用
inline const char* max(const char* x, const char* y)
{
    return std::strcmp(*x, *y) > 0 ? x : y;
}

// 传递引用
// inline const char* const& max(const char* const& x, 
//const char* const& y)
// {
//  return std::strcmp(*x, *y) > 0 ? x : y;
// }

template<typename T>
inline const T& max(const T& x, const T& y, const T& z)
{
    return ::max(::max(x, y), z)
}

客户端:

const char* s1 = "frederic";
const char* s2 = "anica";
const char* s3 = "lucas";
::max(s1, s2, s3);      // 错误,

问题在于:如果你对3个C-strings调用max(),

return ::max(::max(s1, s2), s3);

由于max(s1, s2)返回的是指针而非引用,故将创建一个新的临时局部对象,该值有可能会被外层的max函数以传引用的方式返回,而导致无效的引用。

更多推荐

C++基础——函数模板