Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't access private member in templated overloaded operator

In this code, why is it not possible to access the private field of my class in the operator overload ?

(Note that this is only a MRE, not the full code)

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);
}

When I compile the compiler tells me that it is not possible to access the denominator and numerator fields because they are private. However the overload is indicated as friendly. The class is also indicated as friendly so that all instances of the class whatever the type are friendly.

Could someone explain me what the problem is and how to solve it?

like image 407
CoJa Avatar asked Oct 27 '25 13:10

CoJa


2 Answers

To make each instance of

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

a friend, you need to be just as verbose in your friend declaration. Copy this declaration and stick "friend" in it. There are two quirks. First, template has to come before friend, so you'll be adding the keyword in the middle of the declaration. Second, T is already being used as the template parameter to the class, so you should choose a different identifier to avoid shadowing (I'll use S).

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

Without this change, you are saying that the friend of Frac<T> is an operator that takes two Frac<T> parameters (the same T).

like image 152
JaMiT Avatar answered Oct 29 '25 03:10

JaMiT


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

Yes, you haven't made the free functions friends. You've made the classes friends, but that doesn't help the free functions. One simpler solution is to define them in the class definition.

Example:

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

However, operator+ could be implemented as a free function without any friendship if you instead make operator+= a member function. The friendship between all Frac<>s has already been established so no additional friend declarations are needed.

Example:

#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)
}
like image 21
Ted Lyngmo Avatar answered Oct 29 '25 03:10

Ted Lyngmo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!