无法访问模板化重载运算符中的私有成员

Can't access private member in templated overloaded operator

在这段代码中,为什么无法在运算符重载中访问我的 class 的私有字段?

(注意这只是一个MRE,不是完整代码)

template <typename T>
class Frac

template <typename T, typename U>
Frac<T> operator+ (Frac<T> lhs,const Frac<U>& rhs);

template <typename T, typename U>
bool operator==(const Frac<T>& lhs, const Frac<U>& rhs);

template <typename T>
class Frac {

    template<typename>
    friend class Frac;

    friend Frac operator+ <>(Frac lhs,const Frac& rhs);

    friend bool operator== <>(const Frac& lhs, const Frac& rhs);

    private:
        T numerator, denominator;
};

template <typename T, typename U>
Frac<T> operator+(Frac<T> lhs,const Frac<U>& rhs) {
    lhs.denominator += rhs.denominator;
    return lhs;
}

template <typename T, typename U>
bool operator==(const Frac<T>& lhs, const Frac<U>& rhs) {
    return (lhs.numerator == rhs.numerator && lhs.denominator == rhs.denominator);
}

当我编译时,编译器告诉我无法访问分母和分子字段,因为它们是私有的。但是,过载显示为友好。 class 也被指示为友好的,因此 class 的所有实例无论类型如何都是友好的。

有人可以向我解释问题是什么以及如何解决吗?

制作

的每个实例
template <typename T, typename U>
bool operator==(const Frac<T>& lhs, const Frac<U>& rhs);

朋友,您的 friend 声明需要同样冗长。复制此声明并在其中粘贴“friend”。有两个怪癖。首先,template 必须在 friend 之前,因此您将在声明的中间添加关键字。其次,T 已被用作 class 的模板参数,因此您应该选择不同的标识符以避免隐藏(我将使用 S)。

    template <typename S, typename U>
//                    ^^^
    friend bool operator==(const Frac<S>& lhs, const Frac<U>& rhs);
//  ^^^^^^                           ^^^

如果不做这个改变,你是说 Frac<T> 的朋友是一个带有两个 Frac<T> 参数的运算符(与 T 相同)。

it is not possible to access the denominator and numerator fields because they are private.

是的,您还没有制作免费功能friend。您已经创建了 classes friends,但这对免费功能没有帮助。一种更简单的解决方案是在 class 定义中定义它们。

示例:

    template <typename U>
    friend Frac operator+(Frac lhs, const Frac<U>& rhs) {
        lhs.denominator += rhs.denominator;
        return lhs;        
    }

但是,如果您将 operator+= 设为成员函数,则 operator+ 可以在没有任何 friendship 的情况下实现为自由函数。所有 Frac<> 之间的 friendship 已经建立,因此不需要额外的 friend 声明。

示例:

#include <iostream>
#include <utility>

template <typename T>
class Frac {
public:
    template <typename>   // be friends with all Frac's
    friend class Frac;

    Frac() = default; // needed because for the templated converting ctor below

    // a converting constructor from any Frac:
    template<class U>
    explicit Frac(const Frac<U>& rhs) :
        numerator(rhs.numerator), denominator(rhs.denominator) {}

    template <typename U>
    Frac& operator+=(const Frac<U>& rhs) {
        denominator += rhs.denominator;     // ok: rhs has befriended Frac<T>
        return *this;
    }

    template <typename U>
    bool operator==(const Frac<U>& rhs) const {
        // ok: rhs has befriended Frac<T> here too
        return numerator == rhs.numerator && denominator == rhs.denominator;
    }

private:
    T numerator{}, denominator{};
};

// This free function doesn't need to be a friend. It uses the member function
// operator+=
// The returned type, Fact<R>, is deduced by fetching the type you'd gotten
// if you add a T and U.
template<typename T, typename U,
         typename R = decltype(std::declval<T>() + std::declval<U>())>
Frac<R> operator+(const Frac<T>& lhs, const Frac<U>& rhs) {
    Frac<R> rv(lhs); // use the converting constructor
    rv += rhs;
    return rv;
}

int main() {
    Frac<int> foo;
    Frac<double> bar;

    auto r = foo + bar; // r is a Frac<double> (int + double => double)
}