Template operator<<

Hey guys, I'd like some advice on how to make an ostream operator<< for a template. Stroustrup said templates tend to lead to phenomenally bad error messages, and he wasn't lying. Note that this is just a basic template class for practicing templates, and this is what I have:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  template<typename T, typename A>
class Pair {
public:
	Pair(T p1, A p2) :pair1{ p1 }, pair2{ p2 } { std::cout << "Constructing pair...\n"; }
	~Pair() { std::cout << "Destroying pair...\n"; }

	std::ostream& operator<<(std::ostream& os) //This is what I want
	{
		os << pair1 << ' ' << pair2 << std::endl;
		return os;
	}

	T get_pair1() const { return pair1; }
	A get_pair2() const { return pair2; }
private:
	T pair1;
	A pair2;
};


I get 4 errors, all of which are the result of the operator<<. When I remove it, it works fine, but I need to use the get_pair() functions, which I really don't want to do and kind of defeats the entire point.

unexpected token(s) preceding '{'; skipping apparent function body
unable to recover from previous error(s);
syntax error: missing ';' before '&'
missing type specifier - int assumed. Note: C++ does not support default-int

On the plus side, keeping it in the class definition itself seems to help (down from 105 errors...) but nothing I know to do seems to get it done properly, and I don't know what to make of the errors. I can only assume it has to do with defining in the class, but with templates and the inability to use separate compilation, I have no idea where to begin.
Hi,

The following is a bit of a guess:

unexpected token(s) preceding '{'; skipping apparent function body

Was that the error message verbatim? You should always include the line numbers.

In the initializer list try parentheses (direct initialization) instead of braces.

3
4
5
public:
	Pair(T p1, A p2) :pair1(p1 ), pair2( p2) { std::cout << "Constructing pair...\n"; }
	~Pair() { std::cout << "Destroying pair...\n"; }


On the plus side, keeping it in the class definition itself seems to help (down from 105 errors...)


Yes, the all the template code must be in the header file.

Edit: I am just working on some code for you.
Last edited on
Was that the error message verbatim? You should always include the line numbers.


Good call. They were all 8, 8, 7, 7, respectively from above.

In the initializer list try parentheses (direct initialization) instead of braces.


Unfortunately, that didn't fix it either. Same errors as before, I'm afraid.

Yes, the all the template code must be in the header file.


Haha, yeah, found that out the hard way, after many hours of wondering what the problem was... I'm really astonished that it seems to resist even defining in the same file outside the class. Bad enough it all needs to be clustered in one file, breaking the idea of organization (that I love) over its knee with a sickening snap.
Hi,

This seems to work:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <string>

  template<typename T, typename A>
class Pair {
public:
	Pair(T p1, A p2) :pair1(p1), pair2(p2) { std::cout << "Constructing pair...\n"; }
	~Pair() { std::cout << "Destroying pair...\n"; }

	

	T get_pair1() const { return pair1; }
	A get_pair2() const { return pair2; }
private:
	T pair1;
	A pair2;
};

template<typename T, typename A>
std::ostream& operator<<(std::ostream& out, const Pair<T,A>& obj)
{
   return out << obj.get_pair1() << " " << obj.get_pair2() << std::endl;
}



int main() {
    
    Pair<int, int> MyPair(1,2);
    
    std::cout << MyPair;
    
 return 0;   
}



Nothing wrong with having to call the accessor functions. The definition of the operator function cannot be inside of the class because of it's second parameter, so calling the accessor functions becomes necessary. All I did was look at the documentation :+)

http://en.cppreference.com/w/cpp/language/operators


Edit: It's bedtime at my end, so I hope my post helped you out a bit :+) Regards .....
Last edited on
Yeah that'll definitely work. I was just hoping I could get away with avoiding the mention of a template argument and the object, if that makes sense? Though I suspect it's impossible (being a class template itself - likely the cause of the issues to begin with) that was the idea, anyway.

To be honest though? I do much prefer your version - where I just call the appropriate get() functions. The only reason I even ask (or care) is because APPARENTLY some people think it's bad practice to have get() and set() functions in classes. "It breaks encapsulation!" they say. I mean, fair enough, but going through the book, "Programming Principles and Practice" and looking at the vector example he provides, we're still breaking it in in effectively the same manner, just without an ugly get() or set() function, and instead replacing those with operator[], so what's the difference, you know?
> The definition of the operator function cannot be inside of the class because
> of it's second parameter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template<typename T, typename A>
class Pair {
public:
	Pair(T p1, A p2) :pair1(p1), pair2(p2) { std::cout << "Constructing pair...\n"; }
	~Pair() { std::cout << "Destroying pair...\n"; }

	friend std::ostream& operator<<(std::ostream& out, const Pair<T,A>& obj)
	{
		return out << obj.pair1 //c++, where a friend has access to your private members.
			<< " " << obj.pair2 << std::endl;
	}
	

	T get_pair1() const { return pair1; }
	A get_pair2() const { return pair2; }
private:
	T pair1;
	A pair2;
};
however, there are more interesting things to do that only print state.



> looking at the vector example he provides, we're still breaking it in in
> effectively the same manner, just without an ugly get() or set() function,
> and instead replacing those with operator[], so what's the difference, you know?
A container simply contains objects, and so, it ought to provide some form of access to said objects.
You should note that it does not expose its implementation details.
Perhaps a vector is too simply, take a list instead. You may access the objects, but no the nodes/cells. You may not modify the `next' or `previous' pointers, but you may split and merge.
@ne555

Ah, yes using friend is better - not sure why i keep forgetting about friend :+)

Cheers :+D
Topic archived. No new replies allowed.