3

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?

digito_evo
  • 3,216
  • 2
  • 14
  • 42
Lion King
  • 32,851
  • 25
  • 81
  • 143
  • 2
    `friend std::ostream& operator<<(std::ostream& os, Property& other);` is not a template, so the function you've declared in `class.cpp` is not related to it. – NathanOliver Apr 19 '22 at 16:06
  • Even if the declaration and definition *did* match, [Why can templates only be implemented in the header file?](https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa) – molbdnilo Apr 19 '22 at 16:30
  • When I put them all together in a .h file everything works fine and that I know, but my question depends on the separation of the declaration from the definition, is that possible, and how? – Lion King Apr 19 '22 at 16:59
  • I think the Q has been wrongly marked as [this Q](https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file). The author has already used the technique in the Q by declaring `template class Property;` in the .cpp file. But the problem is something else. The problem is that the author has not instantiated the operator<<. With using it, it will work. – TonySalimi Apr 19 '22 at 17:07
  • Add `template std::ostream& operator<<(std::ostream& os, Property& other);`at the end of your .cpp file. In addition, declare your `friend operator <<` as a templated one. Then it will work. – TonySalimi Apr 19 '22 at 17:10

1 Answers1

4

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<<.

Jason
  • 36,170
  • 5
  • 26
  • 60
  • 1
    Thank you for your answer. but my question depends on the separation of the declaration from the definition (.h,.cpp), I know that when put all together into a .h file everything will work. – Lion King Apr 19 '22 at 16:55
  • @LionKing You said: *"I know that when put all together into a .h file everything will work"*. No, even if you put everything into same header file, your given snippet will not work. You'll still get linker error mentioned at the beginning of your question. [Demo](https://wandbox.org/permlink/no1nyoLUz57RDh2H). The reason i have explained in my answer which is that the friend declaration does not matches the definition because the friend declaration is for an ordinary function while the definition outside the class template is for a template function. – Jason Apr 19 '22 at 17:38
  • @LionKing If you want to keep the implementation separate in the source file then you should also add `template std::ostream& operator<<(std::ostream& os, Property& other);` in your source file. See **[Method 3](https://wandbox.org/permlink/kP6jlydEIfIdQ3be)** in my answer. – Jason Apr 19 '22 at 17:52
  • "Method 3" is working fine, but I have some inquiries about the modification that you made: (1)- In `class .h` why did you put "template" before the declaration of `operator<<` and why did you put a different template name `T` and not `PropeprtyType`? (2)- Why did you remove `const` from parameter two of the friend function `operator<<`? (3)- What is the the benefit of this line `template std::ostream& operator<<(std::ostream& os, Property& other);` although we have put `template class Property;` previously? (4)- And finally, are those rules work on all compilers? – Lion King Apr 19 '22 at 18:20
  • @LionKing **1)** The separate parameter clause `template` is needed so that the friend declaration is for a function template and not for ordinary function. Moreover, we specify the type parameter as `T` and not `PropertyType` because otherwise the new `PropertyType` will *shadow* the outer `PropertyType`. **2)** I removed the `const` from the second parameter accidentally when copy pasting code from your original example. I have added the `const` back in my edited version's method 3. Check out my edited method 3. – Jason Apr 19 '22 at 18:31
  • @LionKing **3)** We added the `template std::ostream& operator<<(std::ostream& os, Property& other);` because note that the overloaded `operator<<` is a non-member function. Now you may be wondering why we did not do the same for `operator=`. Because the overloaded `operator=` is a member function. **4)** Yes, the solution shown in my answer will work for all compilers. – Jason Apr 19 '22 at 18:37
  • @LionKing You're welcome. I added the same explanation as given in the comment to my answer so that if comments get deleted then future readers can still find out why the changes were made. And i voted to reopen your question, so that if people want they can close it with the correct dup unlike last time. – Jason Apr 20 '22 at 03:48