C++ - How to declare a function template friend for a class template [duplicate]
Asked Answered
S

2

7

I have a class template that will output a list of objects stored in the array. I am getting the following error and I am confused where the error is caused since the error is in the .obj and .exe file.

1 unresolved externals (proj08.exe line 1)
unresolved external symbol "class std::basic_ostream > & __cdecl operator<<(class std::basic_ostream > &,class MyVector)" (??6@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@std@@AAV01@V?$MyVector@N@@@Z) referenced in function _main (porj08.obj line 1)

proj08.cpp

#include "stdafx.h"
#include <string>
#include "MyVector.h"

const double FRACTION = 0.5; 

int main()
{
    cout << "\nCreating a vector of doubles named Sam\n";
    MyVector<double> sam;

    cout << "\nPush 12 values into the vector.";
    for (int i = 0; i < 12; i++)
        sam.push_back(i + FRACTION);

    cout << "\nHere is sam: ";
    cout << sam;
    cout << "\n---------------\n";

    cout << "\nCreating an empty vector named joe";
    MyVector<double> joe;

    // test assignment
    joe = sam;

    cout << "\nHere is joe after doing an assignment:\n ";
    cout << joe;
    cout << "\n---------------\n";

    // test the copy constructor
    MyVector<double> bill = sam;

    cout << "\nHere is bill after creating it using the copy constructor:\n ";
    cout << bill;
    cout << "\n---------------\n";

    cout << endl;
    system("PAUSE");
    return 0;
}

MyVector.h

#pragma once
#include <iostream>
#include "stdafx.h"

using namespace std;

template <class T>
class MyVector
{

private:
    int vectorSize;
    int vectorCapacity;
    T *vectorArray;

public:
    MyVector() {
        vectorArray = new T[10];
    }
    T size();
    T capacity();
    void clear();
    void push_back(T n);
    T at(int n);

    friend ostream& operator<<(ostream& os, MyVector<T> vt);

    MyVector<T> operator=(MyVector<T>&);
};

/*
 * TEMPLATE FUNCTIONS
 */

//Return array size
template<class T>
T MyVector<T>::size()
{
    return vectorSize;
}

// Return array capacity
template<class T>
T MyVector<T>::capacity()
{
    return vectorCapacity;
}

// clear array values
template<class T>
void MyVector<T>::clear()
{
    for (int i = 0; i < vectorSize; i++)
    {
        vectorArray[i] = '\0';
    }

    vectorSize = 0;
    vectorCapacity = 2;
}


// Add number to array and double array size if needed
template<class T>
void MyVector<T>::push_back(T n)
{
    int test = 100;
    if (vectorCapacity > vectorSize)
    {
        vectorArray[vectorSize] = n;
        vectorSize++;

    }
    else {

        if (vectorCapacity == 0) {
            vectorArray = new T[4];
            vectorArray[0] = n;
            vectorCapacity = 4;
            vectorSize++;
        }
        else {

            int newCapacity = vectorCapacity * 2;

            // Dynamically allocate a new array of integers what is somewhat larger than the existing array.An algorithm that is often used is to double the size of the array.

            int *tempArray = new int[newCapacity];

            // Change capacity to be the capacity of the new array.

            vectorCapacity = newCapacity;

            // Copy all of the numbers from the first array into the second, in sequence.

            for (int i = 0; i < MyVector::size(); i++)
            {
                tempArray[i] = vectorArray[i];
            }

            delete[] vectorArray;
            vectorArray = new T[newCapacity];

            for (int i = 0; i < MyVector::size(); i++)
            {
                vectorArray[i] = tempArray[i];
            }

            delete[] tempArray;

            // Add the new element at the next open slot in the new array.

            vectorArray[vectorSize] = n;

            // Increment the size;

            vectorSize++;

        }
    }
}

// Return Value and given point in array
template<class T>
T MyVector<T>::at(int n)
{
    return vectorArray[n];
}

// Set one vector to equil another
template<class T>
MyVector<T> MyVector<T>::operator=(MyVector<T>& right) {

    if (vectorCapacity < right.vectorCapacity) {
        if (vectorCapacity != 0)
            delete[] vectorArray;
        vectorArray = new T[right.vectorCapacity];
        vectorCapacity = right.vectorCapacity;
    }
    vectorSize = right.size();

    // Assign values from left to right
    for (int i = 0; i < vectorSize; i++)
    {
        vectorArray[i] = right.at(i);
    }

    return *this;
}

// Cout Vector
template<class T>
ostream& operator << (ostream& os, MyVector<T> vt)
{
    T size = vt.size();

    for (T i = 0; i < size; i++) {

        os << "index " << i << " is " << vt.at(i) << endl;

    }

    return os;
}
Steam answered 22/11, 2015 at 22:42 Comment(1)
As a side comment, consider passing MyVector<T> vt by const reference, friend ostream& operator<<(ostream& os, const MyVector<T>& vt), so you don't make an additional gratuitous copy.Watteau
R
16

You have to declare friend as a function template if you want to match the one you defined:

template <typename U> // use U, so it doesn't clash with T
friend ostream& operator<<(ostream& os, MyVector<U> vt);

If a friend function is declared for a class template, that does not make it a function template.

Rigorous answered 22/11, 2015 at 22:45 Comment(4)
An alternative is to define it inline inside the class. In that case, the function is found via ADL (the definition is visible in the outside scope only via ADL).Watteau
Another alternative is to not use the friend declaration at all, since the implementation only uses public membersParasitology
Well, there's that :DRigorous
The only issue here is that the function in MyVector<int> class is also friends with MyVector<double>, MyVector<char> and so on with other instances of the templated class. See web.mst.edu/~nmjxv3/articles/templates.htmlJulie
W
7

@LogicStuff's answer is perfectly valid. I'd like to clarify what exactly happens in your code and how you may avoid it using an alternative, as I believe most C++ programmers bumped at least once into this issue. Basically, when the template is instantiated, say

MyVector<int> something;

then automatically the friend declaration is bounded to the template type, in this case int, so the compiler generates

friend ostream& operator<<(ostream& os, MyVector<int> vt);

However, this is just a declaration. Your latter definition

template<class T>
ostream& operator << (ostream& os, MyVector<T> vt)

has no relation whatsoever with the one for int, and because the former is a better match, whenever you attempt

cout << something;

the compiler tries to invoke the int version (which has no definition). So you get a linker error.

An alternative widely used is to define your operator inline in class, like

friend ostream& operator << (ostream& os, MyVector<T> vt)
{
    T size = vt.size();

    for (T i = 0; i < size; i++) {

        os << "index " << i << " is " << vt.at(i) << endl;

    }

    return os;
}

Now, each instantiation of your MyVect produces a valid definition of operator<< bounded to the corresponding template type. Note that the operator itself is not a member function, and it is visible in the global namespace only via Argument Dependent Lookup (ADL). This trick is called friend name injection, used widely in the Barton–Nackman trick, and you are able to successfully use it because in a call like

cout << something;

the call is translated to

operator<< (std::cout, something)

and becuase something is of type MyVector<int>, the definition of operator<< is found via ADL. If your operator<< takes e.g. an int as a second parameter, it won't be found via ADL, since fundamental types do not have an associated namespace.

Watteau answered 22/11, 2015 at 23:9 Comment(1)
So it's rather a fault of definition that it doesn't match the declaration. If operator<< (friend) was directly defined at that place, the could would be valid.Rigorous

© 2022 - 2024 — McMap. All rights reserved.