Skip to main content
GameDev.net gamedev.net
🔒 Locked

Template class and streams

Started by Dirk Gregorius May 11, 2004 at 10:05 AM 5 replies 350+ views
Original Post
Dirk Gregorius
Dirk Gregorius
Hi, I have a problem with a simple class template for which I like to implement the stream operators. Here is the code: RasterLayer.h
template<typename T>
class RasterLayer
{
public:
        // Streams

	friend std::ostream& operator<<( std::ostream& os, const RasterLayer<T>& src );

private:
	// Data members

	unsigned int m_Width, m_Height;
	std::vector<T> m_Data;
};

RasterLayer.inl

// Stream construction

template<typename T>
RasterLayer<T>::RasterLayer( std::ifstream& is )
{
	// Read header

	std::string dummy;

	is >> dummy >> m_Width;
	is >> dummy >> m_Height;
	is >> dummy >> dummy;
	is >> dummy >> dummy;
	is >> dummy >> dummy;
	is >> dummy >> dummy;

	// Allocate required memory

	unsigned int nbElements = m_Width * m_Height;
	m_Data.resize( nbElements );

	// Read data

	for ( unsigned int i = 0; i < nbElements; ++i )
	{
		is >> m_Data[i];
	}
}
RasterLayer.inl is included at the end of RasterLayer.h. When I try now to write something like this:
RasterLayer<int> aLayer; // This should create an instance of << ?
std::cout << aLayer; 
The linker tells me that he can't resolve the << operator? Any help is greatly appreciated! -Dirk BTW: I looked into the C++ Primer by Lippman and this is the way he suggest how to implement this! [Edit: Use the [source], Luke.] [edited by - Oluseyi on May 11, 2004 11:18:22 AM]
Oluseyi
Oluseyi
You haven''t implemented the insertion operator:

template <typename T>
std::ostream & operator << (std::ostream &os, const RasterLayer & r)
{
// insert member variables, any necessary formatting, into os


return os;
}

Declaration of a function without definition always leads to an undefined reference.
fallenang3l
fallenang3l
That will not work Oluseyi because the insertion operator is not a template. The best way to make it work is to provide an inline definition because every instance is going to get its insertion operator. The second way to do it would be like this:


template <class T> class RasterLayer;

template <class T>
std::ostream& operator <<(ostream& os, const RasterLayer<T>& rhs);

template <class T>
class RasterLayer
{
friend std::ostream& operator << <>(ostream& os, const RasterLayer<T>& rhs);
};

template <class T>
std::ostream& operator <<(ostream& os, const RasterLayer<T>& rhs)
{
//...

}



With the second method you won't be able to implicitly convert to RasterLayer because templates don't allow that.

[edited by - fallenang3l on May 11, 2004 12:06:57 PM]
Dirk Gregorius
Dirk Gregorius
Nope, both doesn't work. It seems as if the linker doesn't find the insertion opertator. I paste in the whole code - maybe someone finds the time to have a look. This would really help a lot. The project is a simple console apllication and I am using VS.NET 2003


//-------------------------------------------------------------------------------

// RasterLayer.h

//

// Description:

//

// Notes:

//

//

// Copyright(C) 2004 by C. Gattke and D. Gregorius. All rights reserved.

//-------------------------------------------------------------------------------

#pragma once

#ifndef RASTERLAYER_H
#define RASTERLAYER_H

//-------------------------------------------------------------------------------

// Dependencies

//-------------------------------------------------------------------------------

#include <string>
#include <vector>
#include <algorithm>
#include <iostream>
#include <fstream>
#include <cassert>

template <typename T>
class RasterLayer;

template <typename T>
std::ostream& operator <<( std::ostream&, const RasterLayer<T>& );

//-------------------------------------------------------------------------------

// Classes

//-------------------------------------------------------------------------------

template<typename T>
class RasterLayer
{
public:
// Construction

RasterLayer<T>( void );
RasterLayer<T>( std::ifstream& is );
RasterLayer<T>( const RasterLayer<T>& other );

// Assignment

RasterLayer<T>& operator=( const RasterLayer<T>& other );

// Accessors

unsigned int GetWidth( void ) const;
unsigned int GetHeight( void ) const;

// Streams

friend std::ostream& operator<<( std::ostream& os, const RasterLayer<T>& src );

private:
// Implementation functions

void Copy( const RasterLayer<T>& other );

// Data members

unsigned int m_Width, m_Height;
std::vector<T> m_Data;
};

#include "RasterLayer.inl"

//-------------------------------------------------------------------------------

// Typedefs

//-------------------------------------------------------------------------------

typedef RasterLayer<short> ShortLayer;
typedef RasterLayer<int> IntLayer;
typedef RasterLayer<float> FloatLayer;
typedef RasterLayer<double> DoubleLayer;

#endif
</code>

<code>
//-------------------------------------------------------------------------------

// RasterLayer.inl

//

// RasterLayer class inline implementations

//

// Copyright(C) 2004 by C. Gattke and D. Gregorius. All rights reserved.

//-------------------------------------------------------------------------------


// Standard construction

template<typename T>
RasterLayer<T>::RasterLayer( void ) : m_Width( 0 ), m_Height( 0 )
{
}

// Stream construction

template<typename T>
RasterLayer<T>::RasterLayer( std::ifstream& is )
{
// Read header

std::string dummy;

is >> dummy >> m_Width;
is >> dummy >> m_Height;
is >> dummy >> dummy;
is >> dummy >> dummy;
is >> dummy >> dummy;
is >> dummy >> dummy;

// Allocate required memory

unsigned int nbElements = m_Width * m_Height;
m_Data.resize( nbElements );

// Read data

for ( unsigned int i = 0; i < nbElements; ++i )
{
is >> m_Data[i];
}
}

// Copy construction

template<typename T>
RasterLayer<T>::RasterLayer( const RasterLayer<T>& other )
{
Copy( other );
}

// Assignment operator

template<typename T>
RasterLayer<T>& RasterLayer<T>::operator=( const RasterLayer<T>& other )
{
if ( &other != this )
Copy( other );

return *this;
}

// Width accessor

template<typename T>
unsigned int RasterLayer<T>::GetWidth( void ) const
{
return m_Width;
}

// Height accessor

template<typename T>
unsigned int RasterLayer<T>::GetHeight( void ) const
{
return m_Height;
}

// Input stream

template<typename T>
std::ostream& operator<<( std::ostream& os, const RasterLayer<T>& src )
{

static const char* NUM_COLUMS( "ncols" );
static const char* NUM_ROWS( "nrows" );
static const char* X_CORNER( "xllcorner" );
static const char* Y_CORNER( "yllcorner" );
static const char* CELL_SIZE( "cellsize" );
static const char* NO_DATA_VALUE( "NODATA_value" );

// Write header

using std::endl;
os << NUM_COLUMS << src.m_Width << endl;
os << NUM_ROWS << src.m_Height << endl;
os << X_CORNER << 0.0 << endl;
os << Y_CORNER << 0.0 << endl;
os << CELL_SIZE << 0 << endl;
os << NO_DATA_VALUE << 0 << endl;

for ( int i = 0; i < src.m_Height; ++i )
{
for ( int j = 0; < src.m_Width; ++j )
{
os << src.m_Data[src.m_Width * i + j];
}
os << endl;
}

return os;
}

// Copy function

template<typename T>
void RasterLayer<T>::Copy( const RasterLayer<T>& other )
{
m_Width = other.m_Width;
m_Height = other.m_Height;

m_Data.resize( m_Width * m_Height );

std::copy( other.m_Data.begin(), other.m_Data.end(), m_Data.begin() );
}
</code>

<code>
#include "RasterLayer.h"

const char* INPUT_STREAM( "sub_demo500.asc" );
const char* OUTPUT_STREAM( "test.asc" );

int main( void )
{
std::ifstream is( INPUT_STREAM );
if ( !is.good() )
return -1;

IntLayer aLayer1( is );
IntLayer aLayer2( aLayer1 );

std::ofstream os( OUTPUT_STREAM );
os << aLayer1;

return 0;
}


Best regards

-Dirk

edit: [source] tags are your friends.


[edited by - SiCrane on May 11, 2004 1:57:18 PM]
SiCrane
SiCrane
Making template functions friends of a class is not a happy experience, to the point where it isn''t (IMO) worth bothering to try doing. Refactor your code so you don''t have to deal with friendship and the linker errors should disappear.

One way to do this is to remove the forward declaration for the function, and the friend declaration. Add a new public member function like:
std::ostream & dump(std::ostream & out) const;
Put all the stream output functionality that was in operator<<() into the dump() function, then change the body of the insertion operator to return src.dump(os);
fallenang3l
fallenang3l
SiCrane is right. However, your program doesn''t work possibly because you didn''t copy my example verbatim. Take a look at how I declared the friend function in the class.
Dirk Gregorius
Dirk Gregorius
Thanx guys,

everything works fine now.

@fallenang3l:

You are right. I forgot the empty template argument in the friend declaration. Because of this the function wasn't a template function and was not associated with the function implementation - if I understood this correct.

@SiCrane

This is a nice solution. I think the big advantage is that I can declare the dump() function virtual and this way the stream operator is also defined for derived classes. Is this correct?


BTW: I also found another solution that works ( or at least compiles :-) )

You can do the following:


template<class T>
class Foo
{

template<class S>
friend ostream& operator<<( void )(...)

}

template<class S>
ostream& operator<<( void )(...)
{
// Implementation here...

return os;
}


Best regards

-Dirk

[edited by - DonDickieD on May 12, 2004 6:36:13 AM]

[edited by - DonDickieD on May 12, 2004 6:37:34 AM]

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.