c++templatesoperator-overloadingfriend-function

How do we declare a friend function with a class template into .h file and define them into a .cpp file (not all in one header file)?


When separating the declaration/definition of (a friend function + a class template) an error occurs:
error LNK2001: unresolved external symbol "class std::basic_ostream<char,struct std::char_traits<char> > & __cdecl operator<<(class std::basic_ostream<char,struct std::char_traits<char> > &,class Property<int> const &)" (??6@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@std@@AAV01@ABV?$Property@H@@@Z)

Note that when using a friend function without a class template or vice versa everything works fine but when using the two things together an error occurs.

I tried to instantiate the template in the .cpp file but it didn't succeed.
Also, I have included the .cpp file at end of the .h file but it didn't work either.

class.h

template <class PropertyType>
class Property {
    PropertyType m_property;
public:
    const PropertyType& operator=(const PropertyType& value);
    friend std::ostream& operator<<(std::ostream& os, Property<PropertyType>& other);
}; 

class.cpp

template <class PropertyType>
const PropertyType& Property<PropertyType>::operator=(const PropertyType& value) {
    m_property = value;
    return m_property;
}

template <class PropertyType>
std::ostream& operator<<(std::ostream& os, Property<PropertyType>& other) {
    os << other.m_property;
    return os;
}

template Property<int>;

main.cpp

int main() {
    Property<int> num;
    num = 100;
    std::cout << num << "\n";
}

What is wrong with them when separating the declaration/definition into two files and they are used together?


Solution

  • There are two problems with your code snippet.

    The first problem is that you've put the implementation in the source file instead of the header file. So to solve this just move the implementation into the header file.

    The second problem is that even if you move the implementation into the source file the program will still not work(Demo). This is because the friend declaration that you currently have(for overloaded opearator<<), is for an ordinary(non-template) function. That is, in your original code operator<< for class template Property<> is not function template, but “ordinary” function instantiated with the class template if needed. It is what we call a templated entity.

    But the definition that you've provided in the source file(.cpp) for the overloaded operator<< is for a function template and not for an oridnary function. Thus, for the statement std::cout << num << "\n"; the linker cannot find the definition/implementation corresponding to the ordinary overloaded operator<< for which you had the friend declaration.

    There are two ways to solve this:

    Method 1

    Add a separate parameter clause in the friend declaration.

    template <class PropertyType>
    class Property {
        PropertyType m_property;
    public:
        const PropertyType& operator=(const PropertyType& value);
        template<typename T>  //parameter cluase added here
    //---------------------------------------------------vvvvv----------------------->const added here
        friend std::ostream& operator<<(std::ostream& os,const Property<T>& other);
    };
    template <class PropertyType>
    //----------------------------------------vvvvv---------------------------------->const added here
    std::ostream& operator<<(std::ostream& os,const Property<PropertyType>& other) {
        os << other.m_property;
        return os;
    } 
    

    Demo

    Method 2

    Here we provide forward declaration for the overloaded operator<<.

    //forward declaration for class template Property
    template<typename T> class Property;
    
    //forward declaration for overloaded operator<< 
    template<typename T> std::ostream& operator<<(std::ostream&,const Property<T>&);//note the const in the second parameter
    template <class PropertyType>
    class Property {
        PropertyType m_property;
    public:
        const PropertyType& operator=(const PropertyType& value);
    //---------------------------------vvvvvvvvvvvvvv---------------------------------> angle brackets used here
        friend std::ostream& operator<<<PropertyType>(std::ostream& os,const Property<PropertyType>& other);//also note the const in the second parameter
    }; 
    template <class PropertyType>
    const PropertyType& Property<PropertyType>::operator=(const PropertyType& value) {
        m_property = value;
        return m_property;
    }
    
    template <class PropertyType>
    //----------------------------------------vvvvv---------------------------------->const added here 
    std::ostream& operator<<(std::ostream& os,const Property<PropertyType>& other) {
        os << other.m_property;
        return os;
    }
    
    

    Demo

    Method 3

    If you want to provide the implementation inside the source file instead of the header file, then you should add

    template std::ostream& operator<<(std::ostream& os, Property<int>& other);
    

    inside the source file in addition to adding a template parameter clause for the friend declaration as shown below:

    class.h

    #ifndef MYCLASS_H
    #define MYCLASS_H
    #include <iostream>
    template <class PropertyType>
    class Property {
        PropertyType m_property;
    public:
        const PropertyType& operator=(const PropertyType& value);
        template<typename T>  //parameter clause added
    //---------------------------------------------------vvvvv--------------------->const added here
        friend std::ostream& operator<<(std::ostream& os,const Property<T>& other);
    }; 
    
    
    #endif
    

    class.cpp

    #include "class.h"
    
    template <class PropertyType>
    const PropertyType& Property<PropertyType>::operator=(const PropertyType& value) {
        m_property = value;
        return m_property;
    }
    
    template<typename PropertyType>
    //----------------------------------------vvvvv------->const added here
    std::ostream& operator<<(std::ostream& os,const Property<PropertyType>& other) {
        os << other.m_property;
        return os;
    }
    template class Property<int>;
    template std::ostream& operator<<(std::ostream& os,const Property<int>& other);
    
    
    

    main.cpp

    
    #include <iostream>
    
    #include "class.h"
    int main() {
        Property<int> num;
        num = 100;
        std::cout << num << "\n";
    }
    

    Demo

    The changes that I made include:

    1. Added a separate template parameter clause for the friend declaration. This is so that the friend declaration is for a function template. Moreover, we specify a different type parameter named T and not PropertyType because otherwise the new PropertyTypewill shadow the outerPropertyType`.

    2. Added a low-level const to the second parameter of the overloaded opeartor<<.

    3. In method3, inside source file(class.cpp), added

    template std::ostream& operator<<(std::ostream& os,const Property<int>& other);
    

    for the non-member function overloaded operator<<.