26-3 函数模板特化

当为给定类型实例化函数模板时,编译器会复制该模板函数,并将模板类型参数替换为变量声明中使用的实际类型。这意味着对于每个实例类型,特定函数的实现细节都相同(只是使用了不同的类型)。虽然大多数情况下这正是我们想要的,但有时针对特定数据类型略微不同地实现模板函数会很有用。

使用非模板函数

请看以下示例:

#include <iostream>

template <typename T>
void print(const T& t)
{
    std::cout << t << '\n';
}

int main()
{
    print(5);
    print(6.7);

    return 0;
}

打印出来的内容:

img

现在,假设我们想要以科学计数法输出双精度浮点数值(且仅双精度浮点数值)。

为给定类型获取不同行为的一种方法是定义一个非模板函数:

#include <iostream>

template <typename T>
void print(const T& t)
{
    std::cout << t << '\n';
}

void print(double d)
{
    std::cout << std::scientific << d << '\n';
}

int main()
{
    print(5);
    print(6.7);

    return 0;
}

当编译器解析时print(6.7),它会发现print(double)我们已经定义了该函数,并使用该函数而不是从中实例化一个版本print(const T&)。

这将产生以下结果:

img

以这种方式定义函数的一个好处是,非模板函数不需要与函数模板具有相同的签名。请注意,isprint(const T&)使用常量引用传递,而 isprint(double)使用值传递。

通常情况下,如果可以选择,最好定义一个非模板函数

函数模板特化

实现类似结果的另一种方法是使用显式模板特化。显式模板特化(通常简称为模板特化)允许我们为特定类型或值显式定义模板的不同实现。当所有模板参数都被特化时,称为完全特化;当只有部分模板参数被特化时,称为**部分特化。

让我们创建一个关于print何时为Ta的特殊情况double:

#include <iostream>

// Here's our primary template (must come first)
template <typename T>
void print(const T& t)
{
    std::cout << t << '\n';
}

// A full specialization of primary template print<T> for type double
// Full specializations are not implicitly inline, so make this inline if put in header file
template<>                          // template parameter declaration containing no template parameters
void print<double>(const double& d) // specialized for type double
{
    std::cout << std::scientific << d << '\n';
}

int main()
{
    print(5);
    print(6.7);

    return 0;
}

img

为了对模板进行特化,编译器首先必须看到主模板的声明。在上面的示例中,主模板是print(const T&)。

现在,让我们仔细看看函数模板特化:

template<>                          // template parameter declaration containing no template parameters
void print<double>(const double& d) // specialized for type double

首先,我们需要声明模板参数,以便编译器知道我们正在进行与模板相关的操作。然而,在本例中,我们实际上不需要任何模板参数,因此我们使用一对空尖括号。由于特化中没有模板参数,因此这是一个完全特化。

下一行print告诉编译器,我们正在print为类型 T 特化主模板函数double。特化后的函数必须与主模板具有相同的签名(除了特化double后主模板中所有使用 T 的地方T)。由于主模板有一个类型为 T 的参数const T&,因此特化后的函数必须有一个类型为 T 的参数const double&。当主模板使用按引用传递时,特化后的函数不能使用按值传递(反之亦然)。

此示例输出的结果与上述相同。

请注意,如果同时存在匹配的非模板函数和匹配的模板函数特化版本,则非模板函数优先。此外,完整的特化版本并非隐式内联,因此如果您在头文件中定义了特化版本,请确保将inline其包含在内,以避免违反 ODR 规范。

警告:
完整的特化并非隐式内联(部分特化是隐式内联的)。如果将完整的特化放在头文件中,则应将其标记为隐式内联,inline以避免在被多个翻译单元包含时导致 ODR 违规。

= delete就像普通函数一样,如果希望任何解析到特化的函数调用产生编译错误,则可以删除函数模板特化(使用)。

一般来说,应尽可能避免使用函数模板特化,而选择非模板函数。

成员函数的函数模板特化?

现在考虑以下类模板:


#include <iostream>

template <typename T>
class Storage
{
private:
    T m_value {};
public:
    Storage(T value)
      : m_value { value }
    {
    }

    void print()
    {
        std::cout << m_value << '\n';
    }
};

int main()
{
    // Define some storage units
    Storage i { 5 };
    Storage d { 6.7 };

    // Print out some values
    i.print();
    d.print();
}

打印出来的内容:

img

假设我们再次想要创建一个print()以科学计数法打印双精度浮点数的函数。但是,这次print()是一个成员函数,所以我们不能定义一个非成员函数。那么我们该如何实现呢?

虽然看起来我们需要在这里使用函数模板特化,但这并不是正确的工具。请注意,i.print()callsStorage::print()和d.print()calls都调用了 this Storage::print()。因此,如果我们想在 thisdouble 类型时改变此函数的行为T,我们需要特化 this Storage::print(),这是一个类模板特化,而不是函数模板特化!

那么我们该如何实现呢?我们将在下一课中讲解类模板特化。

posted @ 2025-11-29 02:25  游翔  阅读(12)  评论(0)    收藏  举报