2
votes

I tried to separate the declaration and definition of my templated member function of a templated class, but ended up with the following error and warning.

template <typename I>
class BigUnsigned{
    const size_t cell_size=sizeof(I);
    std::vector<I> _integers;
public:
    BigUnsigned();
    BigUnsigned(I);
    friend std::ostream& operator<<(std::ostream& out, const BigUnsigned& bu);
};

std::ostream& operator<<(std::ostream& out, const BigUnsigned& bu){
    for (auto integer : bu._integers){
        out<<integer<<std::endl;
    }
    return out;
}

../hw06/bigunsigned.h:13:77: warning: friend declaration 'std::ostream& operator<<(std::ostream&, const BigUnsigned&)' declares a non-template function [-Wnon-template-friend] friend std::ostream& operator<<(std::ostream& out, const BigUnsigned& bu); ^ ../hw06/bigunsigned.h:13:77: note: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here) ../hw06/bigunsigned.h:16:51: error: invalid use of template-name 'BigUnsigned' without an argument list std::ostream& operator<<(std::ostream& out, const BigUnsigned& bu){ ^ ../hw06/bigunsigned.h: In function 'std::ostream& operator<<(std::ostream&, const int&)': ../hw06/bigunsigned.h:17:28: error: request for member '_integers' in 'bu', which is of non-class type 'const int' for (auto integer : bu._integers){ ^

When I joined the declaration and definition like this, everything compiles fine.

template <typename I>
class BigUnsigned{
    const size_t cell_size=sizeof(I);
    std::vector<I> _integers;
public:
    BigUnsigned();
    BigUnsigned(I);
    friend std::ostream& operator<<(std::ostream& out, const BigUnsigned& bu){
        for (auto integer : bu._integers){
            out<<integer<<std::endl;
        }
        return out;
    }
};

The purpose was to print member variable _integers to cout. What might be the problem?

P.S.: Using this question I made the function free, but did not help.

3
FWIW, for me, it seems a little unexpected for BigUnsigned to be a container. Take that with the grain of salt, though.erip
@erip, why do you think BigUnsigned is a container here? operator<< is a formatting operator. It has nothing to do with containers.Jan Hudec
@JanHudec No, but to store data in a std::vector has everything to do with containers.erip
@JanHudec BigUnsigned<std::string> bu{"Hello, World"}; /* oops, not really a big unsigned after all */erip
@erip, you can't get an arbitrary precision without something of arbitrary size and that something is a vector. As for using std::string for the parameter, presumably the methods not shown require the parameter is a numeric type.Jan Hudec

3 Answers

7
votes

BigUnsigned is a template type so

std::ostream& operator<<(std::ostream& out, const BigUnsigned& bu)

Will not work as there is no BigUnsigned. You need to make the friend function a template so you can take different types of BigUnsigned<some_type>s.

template <typename I>
class BigUnsigned{
    const size_t cell_size=sizeof(I);
    std::vector<I> _integers;
public:
    BigUnsigned();
    BigUnsigned(I);
    template<typename T>
    friend std::ostream& operator<<(std::ostream& out, const BigUnsigned<T>& bu);
};

template<typename T>
std::ostream& operator<<(std::ostream& out, const BigUnsigned<T>& bu){
    for (auto integer : bu._integers){
        out<<integer<<std::endl;
    }
    return out;
}

The reason the second example works is that since it is declared inside the class it uses the template type that the class uses.

4
votes

A refinement to the answer by NathanOliver.

With the other answer, all instantiations of the function template are friends of all instatiations of the class template.

operator<< <int> is a friend of BigUnsigned<int> as well as BigUnsigned<double>.

operator<< <double> is a friend of BigUnsigned<double> as well as BigUnsigned<FooBar>.

You can change the declarations a little bit so that

operator<< <int> is a friend of BigUnsigned<int> but not of BigUnsigned<double>.

operator<< <double> is a friend of BigUnsigned<double> but not BigUnsigned<FooBar>.

// Forward declaration of the class template.
template <typename I> class BigUnsigned;

// Forward declaration of the function template
template <typename I>
std::ostream& operator<<(std::ostream& out, const BigUnsigned<I>& bu);

// Change the friend-ship declaration in the class template.
template <typename I>
class BigUnsigned{
    const size_t cell_size=sizeof(I);
    std::vector<I> _integers;
public:
    BigUnsigned();
    BigUnsigned(I);

    // Grant friend-ship only to a specific instantiation of the
    // function template.
    friend std::ostream& operator<< <I>(std::ostream& out, const BigUnsigned<I>& bu);
};
1
votes

To add a third variant that improves the readability a little bit, is to define the friend function inside the class:

#include <iostream>

template <typename T>
class Foo {
    int test = 42;

    // Note: 'Foo' inside the class body is basically a shortcut for 'Foo<T>'
    // Below line is identical to: friend std::ostream& operator<< (std::ostream &os, Foo<T> const &foo)
    friend std::ostream& operator<< (std::ostream &os, Foo const &foo) {
        return os << foo.test;
    }
};


int main () {
    Foo<int> foo;
    std::cout << foo << '\n';
}