2

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.

Community
  • 1
  • 1
Slazer
  • 4,750
  • 7
  • 33
  • 60
  • FWIW, for _me_, it seems a little unexpected for `BigUnsigned` to be a container. Take that with the grain of salt, though. – erip Jan 21 '16 at 15:56
  • @erip, why do you think `BigUnsigned` is a container here? `operator<<` is a formatting operator. It has nothing to do with containers. – Jan Hudec Jan 21 '16 at 22:20
  • @JanHudec No, but to store data in a `std::vector` has everything to do with containers. – erip Jan 21 '16 at 22:21
  • @JanHudec `BigUnsigned bu{"Hello, World"}; /* oops, not really a big unsigned after all */` – erip Jan 21 '16 at 22:24
  • @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 Jan 21 '16 at 22:34
  • Sure, but with a constructor of the form `BigUnsigned(I);`, I don't think this is going to be the right approach to creating an arbitrary precision object in the first place. I both digress and am happy to disagree. – erip Jan 21 '16 at 22:38
  • One last note: with a member named `_integers`, I might expect it to be a `std::vector` (maybe more appropriately `std::vector`?), thus rendering all templatization useless and clearing up the wonkiness. – erip Jan 21 '16 at 22:41
  • @erip The `BigUnsigned(I)` constructor is clearly a nonesense. I will overload the constructors for each integer data type eventually. As for the typename `I`, the valid values are all the primitive integer types (unsigned char, unsigned int, unsigned short, etc.). Later I might even allow signet types. How to check if `I` is an allowed type when using only a single templated class? Using `typeid` in constructors? – Slazer Jan 22 '16 at 11:31
  • @Slazer Try [`std::is_integral`](http://www.cplusplus.com/reference/type_traits/is_integral/). – erip Jan 22 '16 at 14:36

3 Answers3

7

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.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • @Slazer, actually, you _don't_ want to make the `template std::ostream& operator<<(std::ostream&, const BigUnsigned&)` a friend of `BigUnsigned` for unrelated `I` and `T`, so you _don't_ want to prefix the friend declaration with `typename`. You only need to prefix the out-of-class definition; see R Sahu's answer. – Jan Hudec Jan 21 '16 at 22:19
4

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);
};
Community
  • 1
  • 1
R Sahu
  • 204,454
  • 14
  • 159
  • 270
1

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';
}
Sebastian Mach
  • 38,570
  • 8
  • 95
  • 130