C++ template of vector iterators?

Started by
5 comments, last by M2tM 13 years, 5 months ago
Hi, lets get down to my problem. I have a vision of asking for an iterator to a vector, but I want to get it even if the method itself initially doesn't know which kind of iterator to give back. Is that even possible? I tried templates but it doesn't seem to work..

I'm sure there's support for showing code on this forum (but I dunno how, so here goes):
//MY MAIN	...	poorClass pc();	vector<boo*>::iterator it = pc.getIterator<vector<boo*>::iterator>(TYPE_BOO);	...//END OF MAIN//PART OF THE POORCLASStemplate <typename T>T getIterator(uint8 type){	T result;	switch(type)	{		case TYPE_FOO:		{			result = m_xFoos.begin();		} break;		case TYPE_BOO:		{			result = m_xBoos.begin();		} break;	}	return result;}//END OF CODE

m_xFoos and m_xBoos are my two vectors, storing pointers to whatever classes. When I do this solution I get the C2679, because it can't handle the conversion. What to do? Is there even a solution? Or should I give up killing two birds with one stone?

error C2679: binary '=' : no operator found which takes a right-hand operand of type 'std::_Vector_iterator<_Ty,_Alloc>' (or there is no acceptable conversion)
Advertisement
This isn't very good for the following reasons:

1) Your class is mixing layers of abstraction by having a getIterator function in the first place. Your public member functions should speak in the problem domain your class is meant to solve, "getIterator" is about as useful as a function named "getInt".

2) You are breaking encapsulation by forcing the consumers of your class to understand the exact iterator types of (I'm assuming) private data... This is just conceptually wrong.

3) Even assuming this was a good idea, the function itself is taking a uint8 which has a huge possible range, it should really take a limited type or a named enum, are you using #define to specify the two types available? Don't do that...

4) But it isn't a good idea, this shouldn't be a templated function, it should be broken up into two appropriately named functions because the function is doing two different things! Write functions which do one thing at one layer of abstraction.

5) If you're passing back a begin iterator how will your class consumers know the end of the range!? You'd need to provide an equivalent end iterator and at this point you may as well just make the vectors public...

6) But you shouldn't do that either, instead consider whatever range based behavior you need to implement as either an in-class function which does the iteration and transformation based on passed in parameters, or alternatively supply a callback method which transforms each item similar to std::for_each or other algorithm functions. I don't know what your class is doing, but returning an iterator is a bad idea, returning the contents of the vector is fine.

7) Your function is trying to return two different types... Each version of the template would have an error (if you try to make a template of one type it would have the possibility of returning the other if you supply the wrong value to the parameter which of course is not possible as it results in a compile time error.) You can instantiate a template which returns a specified type provided the return value is consistent, but not if the generated template functions mix and match return types as you are doing here.

IE:

vector<boo*>::iterator it = pc.getIterator<vector<boo*>::iterator>(TYPE_BOO);

Results in a template function like this:

vector<boo*>::iterator getIterator(uint8 type){	vector<boo*>::iterator result;	switch(type)	{		case TYPE_FOO:		{			result = m_xFoos.begin();		} break;		case TYPE_BOO:		{			result = m_xBoos.begin();		} break;	}	return result;}


In this case let's just assume m_xFoos.begin() returns vector<foo*>::iterator, you would get a conversion error when this template function is generated and attempts to compile.

So, not only is this technically wrong, it is also conceptually bad. In a weak typed language which allowed stuff like this to happen it would still be wrong.

Props, however, for the appropriately named "poorClass". :) I kind of laughed a bit at the name. Hopefully this advice isn't too gruff for you! It's just to the point, I think it's great that you've asked it here in the beginner forum and hopefully the answer is helpful.

FINALLY, here is a different implementation that will do what you expect it to based on simple operator overloading called from a template, do not use it, but enjoy!

template <typename T>T getIterator(){    T returnIterator;    setIterator(returnIterator);    return returnIterator;}private:void setIterator(vector<boo*>::iterator &i){    i = m_xBoos.begin();}void setIterator(vector<foo*>::iterator &i){    i = m_xFoos.begin();}


You could also simply cut out the template and use the setIterator functions directly, but instead of returning an iterator you'd have to make one first and pass it in.

I'm not near a compiler right now, so I apologize for any problems with it. Calling syntax would be:

vector<boo*>::iterator it = pc.getIterator<vector<boo*>::iterator>();

If you made the setIterator methods public you could just do this:

vector<boo*>::iterator it;
pc.setIterator(it);

Of course you'd want to re-name it because setIterator sounds like you're applying the value of it to something instead of actually returning it.

[Edited by - M2tM on October 21, 2010 8:11:45 PM]
_______________________"You're using a screwdriver to nail some glue to a ming vase. " -ToohrVyk
I totally agree this is the wrong way to do it, but here it is anyway:

#include <iostream>#include <list>#include <vector>template <int ID>struct RtypePicker;template <>struct RtypePicker<0>{	typedef std::vector<int> Container;};template <>struct RtypePicker<1>{	typedef std::list<double> Container;};class C{public:C() {c0.push_back(1);c1.push_back(2.2);}std::vector<int> c0;std::list<double> c1;template <int ID> typename RtypePicker<ID>::Container::iterator getIterator(){	void* containers[2] = {&c0,&c1};	typedef typename RtypePicker<ID>::Container Cont;	Cont* c = (Cont*)containers[ID];	return c->begin(); }};int main(){	C myobj;	std::cout << *myobj.getIterator<0>() << '\n';	std::cout << *myobj.getIterator<1>() << '\n';	return 0;}
Design issues aside, your approach is more complex and more brittle and more code than just defining two simple functions:
vector<foo *>::iterator getFooIterator(){    return foos.begin();}vector<bar *>::iterator getBarIterator(){    return bars.begin();}

Don't over engineer. In this case, the others have pointed out other issues with this approach, so I wouldn't advocate actually using the above.
Wow, thanks for the great help, especially M2tM. Maybe I should apologize for not posting my real problem, instead of a try to recreate it using a no sense example.

I'm making an RPG editor, and at a point at the 3rd tab I realized this class will become too huge, so my methodes needs to share as much as possible, so why not go all the way? Thats how this strange example came up.

my uint8 is:
typedef unsigned char uint8;

M2tM, u said uint8 was too huge range? I've always used it for my enums, is that bad?
enum eTabSelected
{
EDIT_SKILL,
EDIT_ITEM,
EDIT_MONSTER
};

the real purpose of getIterator is to use a char* as parameter (key) and compare it inside the iterator to return either an iterator to the correct element or the element itself, I don't care which. If its iterator I will just cast it when the result is back.

I think I misunderstood the purpose of template, its designed to first set the type, then calculate something independently and return results of the given type. I wanted the type to be unknown which might be impossible (or bad) using templates.

Why I need the call in the first place is because I've got a lot of buttons asking for access to a specific element. Sure, they can all reach the member vector right away, but the code would be so repetative with so many buttons, so I really need a method to take care of the calculation which element to use.

I'm considering taking the easy way rip-off also presented.. Thanks for the help guys!
Quote:Original post by WeedFeeder
M2tM, u said uint8 was too huge range? I've always used it for my enums, is that bad?
enum eTabSelected
{
EDIT_SKILL,
EDIT_ITEM,
EDIT_MONSTER
};

You can use the enum type directly -- eTabSelected -- instead of uint8. For example: T getIterator(eTabSelected type){

This will prevent silly, nonsensical calls like pc.GetIterator<vector<boo*>::iterator>(42); being made by accident, and this makes it easier to understand what you're supposed to be passing in if you don't already know or have forgotten -- you can just look at the parameter type rather than how it's used inside the (possibly large) function.

It will also prevent all your code from suddenly breaking if you ever have a 257th edit option.

Quote:If its iterator I will just cast it when the result is back.

This is the general problem with this approach: Since you have to know what to cast something to for a given entry, you don't end up actually saving much at all on the code front. You may want to consider polymorphism instead -- templates can still be useful for this:

#include <boost/lexical_cast.hpp>#include <boost/utility.hpp>#include <string>#include <map>#include <iostream>class text_editable {public:	virtual std::string get() const = 0;	virtual void set( const std::string& new_value ) = 0;};template < typename T > class editor_entry : public text_editable {public:	T value;	std::string get() const { return boost::lexical_cast<std::string>(value); }	void set( const std::string& new_value ) { value = boost::lexical_cast<T>(new_value); }};class some_editor_tab {public:	editor_entry<std::string> hostname;	editor_entry<int> port;	std::map<std::string,text_editable*> get_text_editables() {		std::map<std::string,text_editable*> entries;		entries["hostname"] = &hostname;		entries["port"]     = &port;		return entries;	}};int main() {	some_editor_tab example;	example.hostname.value = "www.gamedev.net";	example.port    .value = 80;	std::cout << "Editor Fields:\n";	std::map<std::string,text_editable*> fields = example.get_text_editables();	for ( std::map<std::string,text_editable*>::iterator i = fields.begin() ; i != fields.end() ; ++i ) {		std::cout << i->first << "\t= " << i->second->get() << "\n";	}}


We don't really have any need for the enumeration anymore either: If we want to use a specific field, we use it directly. If we want to treat them in a general manner, we use a generic interface so we don't need to know the exact type involved.
Quote:Original post by rip-off
Design issues aside, your approach is more complex and more brittle and more code than just defining two simple functions:
*** Source Snippet Removed ***
Don't over engineer. In this case, the others have pointed out other issues with this approach, so I wouldn't advocate actually using the above.


Yeah, this is basically the implementation I was hinting at in chastisement number 4. :)

MaulingMonkey has an excellent post! :) *edit: Wow, not sure how I slipped up in forgetting to ++ the fellow for so long!
_______________________"You're using a screwdriver to nail some glue to a ming vase. " -ToohrVyk

This topic is closed to new replies.

Advertisement