C++, partially specialized template classes, and friend functions

sweenish

Diamond Member
May 21, 2013
3,656
60
91
Maybe my approach is completely wrong, maybe I'm just missing something. But I cannot figure out for the life of me what I'm doing wrong or not doing.

I have a template class, Pair. It holds two variables of the same type T. I have overloaded the insertion operator as a friend function.

I have a partial specialization of Pair, namely Pair<T*>. I still want to be able to use the << operator on objects of this type.

I know I can't partially specialize the friend function. That would have been too easy, anyway, I guess. I tried adding an extra layer of abstraction wherein the friend function simply calls a private print() function. The problem is that this function is private. If I get rid of the friend function in the partially specialized class, I get an access error. I would prefer it not be public, because I want people to use the operator.

I have not tried going full non-member, non-friend yet with the operator overload because I would also rather not have simple getters in my class.

I would also prefer not to make the all possible instances friends, as it provides too much access.

It seems like I would have to go even more abstract: define an interface class, derive both types from the interface (T and T*). But then I'm stuck dealing in pointers to the interface.

I'm not sure what I'm missing, if anything. And if I have to choose one of the less desirable routes, which one is "best"?

Here's the code:
Code:
#ifndef PAIR_HPP
#define PAIR_HPP

#include <iostream>

// Prep friend function template
template <typename T> class Pair;
template <typename T>
    std::ostream& operator <<(std::ostream& sout, const Pair<T> &rhs);


template <typename T>
class Pair {
public:
    Pair();
    Pair(T one, T two);
    friend std::ostream& operator << <T>(std::ostream& sout, const Pair<T> &rhs);
private:
    T first;
    T second;

    std::ostream& print(std::ostream& sout) const;
};


// Class implementation
template <typename T>
Pair<T>::Pair() : first(T()), second(T()) { }

template <typename T>
Pair<T>::Pair(T one, T two) : first(one), second(two) { }

template <typename T>
std::ostream& operator <<(std::ostream& sout, const Pair<T> &rhs)
{
    return rhs.print(sout);
}

template <typename T>
std::ostream& Pair<T>::print(std::ostream& sout) const
{
    return sout << "(" << first << ", " << second << ")";
}

#include "ptr_Pair.inl"

#endif
 

sweenish

Diamond Member
May 21, 2013
3,656
60
91
And here's the code for the partial specialization:
Code:
// Prep friend function template
template <typename T> class Pair<T*>;
template <typename T>
    std::ostream& operator <<(std::ostream& sout, const Pair<T> &rhs);


template <typename T>
class Pair<T*> {
public:
    Pair();
    Pair(T* one, T* two);
    Pair(const Pair &cpy);
    Pair(Pair &&mov);
    ~Pair();
    Pair<T*>& operator=(const Pair &rhs);
    Pair<T*>& operator=(Pair &&rhs);
    friend std::ostream& operator << <>(std::ostream& sout, const Pair &rhs)
    {
        return rhs.print(sout);
    }
private:
    T* first;
    T* second;

    std::ostream& print(std::ostream &sout) const;
};


// Partial specialization implementation
template <typename T>
Pair<T*>::Pair() : first(new T()), second(new T()) { }

template <typename T>
Pair<T*>::Pair(T* one, T* two) : first(new T(*one)), second(new T(*two)) { }

template <typename T>
Pair<T*>::Pair(const Pair &cpy) : first(new T(*(cpy.first))),
                                  second(new T(*(cpy.second)))
{
}

template <typename T>
Pair<T*>::Pair(Pair &&mov) : first(mov.first), second(mov.second)
{
    mov.first = nullptr;
    mov.second = nullptr;
}

template <typename T>
Pair<T*>::~Pair()
{
    if (first != nullptr)
        delete first;
    
    if (second != nullptr)
        delete second;
    
    first = nullptr;
    second = nullptr;
}

template <typename T>
Pair<T*>& Pair<T*>::operator=(const Pair &rhs)
{
    if (this != &rhs) {
        if (first != nullptr)  { delete first; }
        if (second != nullptr) { delete second; }

        first = new T(*(rhs.first));
        second = new T(*(rhs.second));
    }

    return *this;
}

template <typename T>
Pair<T*>& Pair<T*>::operator=(Pair &&rhs)
{
    if (this != &rhs) {
        if (first != nullptr)  { delete first; }
        if (second != nullptr) { delete second; }

        first = rhs.first;
        second = rhs.second;

        rhs.first = nullptr;
        rhs.second = nullptr;
    }

    return *this;
}

// template <typename T>
// std::ostream& operator << <T>(std::ostream& sout, const Pair<T> &rhs)
// {
//     return rhs.print(sout);
// }

template <typename T>
std::ostream& Pair<T*>::print(std::ostream &sout) const
{
    return sout << "(" << *first << ", " << *second << ")";
}
 

sweenish

Diamond Member
May 21, 2013
3,656
60
91
Finally, may main function:
Code:
#include <iostream>
#include "Pair.hpp"

int main()
{
    Pair<int> ipair(42, 55);
    Pair<int*> iptrpair(new int(42), new int(55));

    std::cout << ipair << std::endl;
    std::cout << iptrpair << std::endl;

    return 0;
}

In its current state, the code does not compile, but I've minimized the errors down to one on g++ 8.2.
 

sweenish

Diamond Member
May 21, 2013
3,656
60
91
Well, it always helps to take a break from frustrating code. Removing <> from my partially specialized friend got it to work just the way I wanted.