0

I have written a custom vector class template and print_vector function template that is supposed to print the content of my custom vector class. The print_vector function uses v.sz and v.elem which are declared private inside the vector class. However I am unable to declare print_vector as a friend inside my vector class. I want to keep the print_vector separately defined in t_vector.cpp.

A problem is that print_vector takes a parameter of the same type as the class it is declared friend in (becomes a circular dependency?). I also get an error saying that print_vector is not declared in scope.

I have marked the problematic places with // <--

// t_vector.h
#ifndef T_VECTOR_H
#define T_VECTOR_H

#include <string>

template <typename T>
void print_vector(Vector<T>& v);

template <typename T>
struct Vector {
private:
    int sz;
    T* elem;

public:
    Vector() = delete;

    explicit Vector(int s);

    Vector(const Vector<T>& v); 

    ~Vector(); 

    Vector<T>& operator=(const Vector<T>& v); 

    T& operator[](int e) const;

    template<typename U>
    friend void print_vector(Vector<U>& v); // <-- TRYING TO FRIEND print_vector
};
#endif
// t_vector.cpp
#include "t_vector.h"

#include <stdexcept>
#include <iostream>
#include <string>
using std::cout;
using std::endl;
using std::string;

template <typename T>
Vector<T>::Vector(int s) {
    if (s < 0) throw std::invalid_argument("Negative size");
    cout << this << " called Vector(int s) constructor" << endl;
    sz = s;
    elem = new T[sz];
}

template <typename T>
Vector<T>::Vector(const Vector<T>& v) : sz{v.sz}, elem{new T[v.sz]} { 
    cout << this << " called Copy constructor on " << &v << endl;
    for (int i = 0; i<sz; ++i) {
        elem[i] = v[i];
    }
}

template <typename T>
Vector<T>::~Vector() {
    cout << "~" << this << " delete elem " << &elem << endl;
    delete[] elem;
}

template <typename T>
Vector<T>& Vector<T>::operator=(const Vector<T>& v) {
    cout << this << " called Copy assignment operator to copy " << &v << endl;
    if (this != &v) {
        T *tmp = new T[v.sz];
        for (int i=0; i<v.sz; ++i ) {
            tmp[i] = v.elem[i];
        }
        sz = v.sz; 
        delete[] elem;
        elem = tmp;
    }
    return *this;
}

template <typename T>
T& Vector<T>::operator[](int e) const {
    if (e < 0 || e >= sz) throw std::out_of_range("Vector::operator[]"); 
    return elem[e];
}

template <typename T>
void print_vector(Vector<T>& v) { // <--- THIS FUNCTION WANTS TO READ v.sz WHICH IS PRIVATE
    for (int i = 0; i < v.sz-1; ++i) {
        cout << '[' << v.elem[i] << ']';
    }
    cout << '[' << v[v.sz-1] << ']' << endl;
}
// test_t_vector.cpp
#include "t_vector.h"
#include "t_vector.cpp" 

#include <iostream>
using std::cout;
using std::endl;

int main() {
    cout << "---------- Starting: test_vector ----------" << endl;

    Vector<int> vec(3);
    
    vec[2] = 5;
    print_vector(vec);

    cout << "---------- Exiting: test_vector ----------" << endl;
    return 0; 
}

Error output:

g++ -O0 -Wall -Wextra -pedantic-errors -Wold-style-cast  -fno-elide-constructors  -std=c++14  -g -ggdb3  -MT t_vector.o -MMD -MP -MF t_vector.d -std=c++14 -I.  -c -o t_vector.o t_vector.cpp
In file included from t_vector.cpp:1:
t_vector.h:7:6: error: variable or field ‘print_vector’ declared void
    7 | void print_vector(Vector<T>& v);
      |      ^~~~~~~~~~~~
t_vector.h:7:19: error: ‘Vector’ was not declared in this scope
    7 | void print_vector(Vector<T>& v);
      |                   ^~~~~~
t_vector.h:7:27: error: expected primary-expression before ‘>’ token
    7 | void print_vector(Vector<T>& v);
      |                           ^
t_vector.h:7:30: error: ‘v’ was not declared in this scope
    7 | void print_vector(Vector<T>& v);
      |                              ^
make: *** [<builtin>: t_vector.o] Error 1
Enigma
  • 53
  • 9
  • 2
    Why not just add a public `size()` function? IMO much better than having to friend anything that wants to know that. You may also want to read [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) – Retired Ninja Aug 11 '21 at 22:43
  • @RetiredNinja that is indeed a much better solution. I am currently trying to learn friend in template so this problem might be a bit artificial. I have included `t_vector.cpp` in the test file, so I think it's possible to define the template inside `t_vector.cpp` – Enigma Aug 11 '21 at 22:54
  • @TedLyngmo Not entirely following what you meant there. Do you mean if I try to think of the templated struct as a templated function instead? – Enigma Aug 11 '21 at 22:59
  • @TedLyngmo Oh I see. I have edited my post now! – Enigma Aug 11 '21 at 23:12
  • 1
    @TedLyngmo Edited it again – Enigma Aug 12 '21 at 00:03
  • 1
    @Enigma Oh, now it's becoming clear. You have a _function template_ in a `.cpp` file. When that translation unit is compiled, what instances of the function template will be put in the object file? It can't put _every possible_ instantiation in there. It will only put _explicit_ instantiations in there. If you have none - there will be none. Have you read [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) - Again, this is where it helps to think of them as function or class templates. – Ted Lyngmo Aug 12 '21 at 00:17
  • @TedLyngmo I read the post more throughly this time. I tried the tpp method and it worked! I am a bit confused though. The implementations were not included in the header before but the class template seemed to work correctly when only defining member functions (without the friend function)? – Enigma Aug 12 '21 at 00:55
  • 1
    You may _get lucky_ in some cases, but the general rule (which isn't a hard rule) is - implement your function and class templates in the header files. – Ted Lyngmo Aug 12 '21 at 01:01
  • @TedLyngmo Thanks for the help, I will post the answer and change the post to resolved! – Enigma Aug 12 '21 at 01:06

1 Answers1

3

As Ted Lyngmo and Retired Ninja mentioned the problem is that the implementation should have been visible in the header file. One of the methods to achieve this is to include a .tpp file with all the template implementations in the header file and include this file in the end of the header file. Unfortunately my class template seemed to work despite separating the class template declaration in the header file and the implementation details in a cpp file. This can occur by luck but the general rule is to show implementation details in header one way or another.

Enigma
  • 53
  • 9
  • 1
    I think you got it. It's also makes writing class templates easier since you can write the member function definitions directly in the class definitions. It's at least easy when you keep the member functions short and sweet (no hard-to-understand magic). That makes it easy to read and maintain. – Ted Lyngmo Aug 12 '21 at 01:23