重载运算符与函数模板

在 《函数模板实例化》中,我们讨论了编译器如何利用函数模板实例化出具体函数,然后再对这些函数进行编译。我们也指出,若函数模板中的代码试图对实参类型执行其不支持的操作(例如将整数 1 与 std::string 相加),则这些实例化出的函数将无法通过编译。 本文将通过若干示例说明:当实际类类型缺少必要运算符时,实例化出的函数无法编译,以及如何为这些类定义相应运算符,使得实例化函数得以顺利编译。

运算符、函数调用与函数模板

首先,创建一个简单类:

class Cents
{
private:
    int m_cents{};
public:
    Cents(int cents)
        : m_cents{ cents }
    {
    }

    friend std::ostream& operator<< (std::ostream& ostr, const Cents& c)
    {
        ostr << c.m_cents;
        return ostr;
    }
};

再定义一个 max 函数模板:

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

接着尝试用 Cents 类型对象调用 max:

#include <iostream>

class Cents
{
private:
    int m_cents{};
public:
    Cents(int cents)
        : m_cents{ cents }
    {
    }

    friend std::ostream& operator<< (std::ostream& ostr, const Cents& c)
    {
        ostr << c.m_cents;
        return ostr;
    }
};

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

int main()
{
    Cents nickel{ 5 };
    Cents dime{ 10 };

    Cents bigger{ max(nickel, dime) };
    std::cout << bigger << " is bigger\n";

    return 0;
}

C++ 将为 max 生成如下模板实例:

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

随后尝试编译此函数。问题显而易见:当 x 与 y 均为 Cents 类型时,C++ 并不知道如何计算 x < y!于是产生编译错误。

解决方法很简单:为任何希望与 max 搭配使用的类重载 operator<:

#include <iostream>

class Cents
{
private:
    int m_cents{};
public:
    Cents(int cents)
        : m_cents{ cents }
    {
    }

    friend bool operator< (const Cents& c1, const Cents& c2)
    {
        return (c1.m_cents < c2.m_cents);
    }

    friend std::ostream& operator<< (std::ostream& ostr, const Cents& c)
    {
        ostr << c.m_cents;
        return ostr;
    }
};

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

int main()
{
    Cents nickel{ 5 };
    Cents dime{ 10 };

    Cents bigger{ max(nickel, dime) };
    std::cout << bigger << " is bigger\n";

    return 0;
}

程序按预期运行,输出:

10 is bigger

再举一例

下面演示另一个由于缺少重载运算符而导致函数模板无法工作的例子。

以下函数模板用于计算数组中若干对象的平均值:

#include <iostream>

template <typename T>
T average(const T* myArray, int numValues)
{
    T sum{ 0 };
    for (int count{ 0 }; count < numValues; ++count)
        sum += myArray[count];

    sum /= numValues;
    return sum;
}

int main()
{
    int intArray[]{ 5, 3, 2, 1, 4 };
    std::cout << average(intArray, 5) << '\n';

    double doubleArray[]{ 3.12, 3.45, 9.23, 6.34 };
    std::cout << average(doubleArray, 4) << '\n';

    return 0;
}

输出:

3
5.535

可见该模板对内建类型运行良好!

现在尝试用 Cents 类调用此函数:

#include <iostream>

template <typename T>
T average(const T* myArray, int numValues)
{
    T sum{ 0 };
    for (int count{ 0 }; count < numValues; ++count)
        sum += myArray[count];

    sum /= numValues;
    return sum;
}

class Cents
{
private:
    int m_cents{};
public:
    Cents(int cents)
        : m_cents{ cents }
    {
    }
};

int main()
{
    Cents centsArray[]{ Cents{ 5 }, Cents{ 10 }, Cents{ 15 }, Cents{ 14 } };
    std::cout << average(centsArray, 4) << '\n';

    return 0;
}

编译器报错,首条错误信息类似:

error C2679: binary '<<': 没有找到接受 Cents 类型右操作数的运算符(或无可接受转换)
回忆可知 average() 返回 Cents 对象,而我们正尝试用 operator<< 将其输出到 std::cout。然而尚未为 Cents 定义此运算符。现在补充:
```cpp
#include <iostream>

template <typename T>
T average(const T* myArray, int numValues)
{
    T sum{ 0 };
    for (int count{ 0 }; count < numValues; ++count)
        sum += myArray[count];

    sum /= numValues;
    return sum;
}

class Cents
{
private:
    int m_cents{};
public:
    Cents(int cents)
        : m_cents{ cents }
    {
    }

    friend std::ostream& operator<< (std::ostream& out, const Cents& cents)
    {
        out << cents.m_cents << " cents ";
        return out;
    }
};

int main()
{
    Cents centsArray[]{ Cents{ 5 }, Cents{ 10 }, Cents{ 15 }, Cents{ 14 } };
    std::cout << average(centsArray, 4) << '\n';

    return 0;
}

再次编译,又会遇到新错误:

error C2676: binary '+=': Cents 未定义此运算符或到预定义运算符可接受类型的转换
该错误源于调用 `average(const Cents*, int)` 时生成的函数模板实例。回忆模板实例化过程:编译器为实参类型“刻出”一份函数副本,将模板形参替换为实参类型。当 T 为 Cents 时,生成的实例为:
```cpp
template <>
Cents average(const Cents* myArray, int numValues)
{
    Cents sum{ 0 };
    for (int count{ 0 }; count < numValues; ++count)
        sum += myArray[count];

    sum /= numValues;
    return sum;
}

报错原因在于:

sum += myArray[count];

此时 sum 为 Cents 对象,而我们尚未为 Cents 定义 operator+=!为使 average() 能与 Cents 协同工作,必须实现该运算符。进一步观察,average() 还使用了 operator/=,于是也一并实现:

#include <iostream>

template <typename T>
T average(const T* myArray, int numValues)
{
    T sum{ 0 };
    for (int count{ 0 }; count < numValues; ++count)
        sum += myArray[count];

    sum /= numValues;
    return sum;
}

class Cents
{
private:
    int m_cents{};
public:
    Cents(int cents)
        : m_cents{ cents }
    {
    }

    friend std::ostream& operator<< (std::ostream& out, const Cents& cents)
    {
        out << cents.m_cents << " cents ";
        return out;
    }

    Cents& operator+= (const Cents& cents)
    {
        m_cents += cents.m_cents;
        return *this;
    }

    Cents& operator/= (int x)
    {
        m_cents /= x;
        return *this;
    }
};

int main()
{
    Cents centsArray[]{ Cents{ 5 }, Cents{ 10 }, Cents{ 15 }, Cents{ 14 } };
    std::cout << average(centsArray, 4) << '\n';

    return 0;
}

最终代码顺利通过编译并运行,结果如下: 11 cents

注意,我们无需对 average() 做任何修改即可使其支持 Cents 类型。只需为 Cents 类实现 average() 所用到的运算符,其余工作由编译器自动完成!

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"