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

loading config file into map

Started by cptrnet Aug 26, 2005 at 11:05 AM 8 replies 850+ views
Original Post
cptrnet
cptrnet
Hi, I am trying to load properties from a config file into a map. What I want to do is have different properties for my application. The thing I don't know how to do is have a template datatype for the value. For example the title of the window is a string and the width and height is a number. I don't want a type casting nightmare. I declare my map as of now like this: std::map properties; Then I just load in the key-value pair from the text file in this format: Title Tetris Width 800 Height 600 etc. Could I do something like this: std::map properties; Is this possible with templates? Or do I need to figure out some other idea?
If you insist on saying "DUH", try to make a post that makes a little more sense
snk_kid
snk_kid
Quote:
Original post by cptrnet
Is this possible with templates?


Not directly no.

Quote:
Original post by cptrnet
Or do I need to figure out some other idea?


Automatic type erasor is one technic but i'd check this first though
bjogio
bjogio
This is how I achieve this in my engine "iltuomondofuturo":
#ifndef _CONFIGURATION_H#define _CONFIGURATION_H#include <sstream>namespace iltuomondofuturo{class ConfigVarBase{protected:	const char* Name;	bool Changed;	explicit ConfigVarBase(const char* Name) :		Name(Name), Changed(false) { }public:	virtual void StringToValue(const std::string& Value) = 0;	virtual std::string ValueToString() = 0;	const char* GetName() { return Name; }	bool IsChanged() { return Changed; }};template<typename T>class ConfigVar : public ConfigVarBase{public:	explicit ConfigVar(const char* Name):		ConfigValueBase(Name) { }	explicit ConfigVar(const char* Name, T Val) :		ConfigValueBase(Name), Value(Val) { }	explicit ConfigVar(const char* Name, const std::string& Value) :		ConfigVarBase(Name) { StringToValue(Value); }	void SetValue(T Val) { Value = Val; }	T GetValue() { return Value; }	void StringToValue(const std::string& Val)	{		std::istringstream str(Value);		str.unsetf(std::ios::skipws);		str.setf(std::ios_base::boolalpha);		str >> Value;	}	std::string ValueToString()	{		std::ostringstream str;		str.unsetf(std::ios::skipws);		str.setf(std::ios_base::boolalpha);		str << Value;		return str.str();	}	ConfigVar<T>& operator = (const T& val)	{		Changed = true;		Value = val;		return *this;	}	operator const T() { return Value; }private :	T Value;};template<>class ConfigVar<std::string> : public ConfigVarBase{public:	explicit ConfigVar<std::string>(const char* Name, const std::string& Val):		ConfigVarBase(Name), Value(Val) { }	explicit ConfigVar<std::string>(const char* Name):		ConfigVarBase(Name) { }	void StringToValue(const std::string& Val) { Value = Val; }	std::string ValueToString() { return Value;}	ConfigVar<std::string>& operator = (const std::string& val)	{		Changed = true;		Value = val;		return *this;	}	operator const std::string() { return (const std::string)Value; }	std::string Value;};template<typename Type, unsigned int Number>class ConfigVarArray : public ConfigVarBase{public:	explicit ConfigVarArray(const char* Name):		ConfigValueBase(Name) { }	explicit ConfigVarArray(const char* Name, const std::string& Value):		ConfigVarBase(Name)	{ StringToValue(Value); }	bool StringToValue(const std::string& Val)	{		unsigned int i = 0;		std::istringstream str(Val);		str.setf(std::ios_base::boolalpha);		while((str >> Value) && (i++ < Number)) { }		return str ? true : false;	}	std::string ValueToString()	{		std::ostringstream str;		str.unsetf(std::ios::skipws);		str.setf(std::ios_base::boolalpha);		for(unsigned int i = 0; i < Number; i++)			str << (Type)Value << ' ';		return str.str();	}	Type& operator[](unsigned int Pos) { return Value[Pos]; }	unsigned int Size() { return Number; }	Type Value[Number];};typedef ConfigVar<int> ConfigInt;typedef ConfigVar<unsigned int> ConfigUnsignedInt;typedef ConfigVar<float> ConfigFloat;typedef ConfigVar<bool> ConfigBool;typedef ConfigVar<std::string> ConfigString;class Configuration{public:	virtual void GetValue(const std::string& Section, ConfigVarBase* Cfg) const = 0;	virtual void SetValue(const std::string& Section, ConfigVarBase* Cfg) = 0;};}#endif

it's a little tricky. I use:
operator const T() { return Value; }
and a template specialization for std::string
and a special ConfigArray for multiple values like:
SomeGoodCVar = foo bar quax etc

I load the values in another class cause in this way I can support xml, ini or whatever else grammar. If you want also that code I can post here.
Hope it helps.
[ILTUOMONDOFUTURO]
cptrnet
cptrnet
Whoa thats looks about as big of pain as type casting.

bjogio thanks for the reply, if you want to post the code that would be cool.
If you insist on saying "DUH", try to make a post that makes a little more sense
snk_kid
snk_kid
Quote:
Original post by bjogio
This is how I achieve this in my engine "iltuomondofuturo":....


That code has some "issues", like SetValue passing arguments by value and GetValue returning by value. It also uses manual type erasor as opposed to automatic type erasor which is much nicer (like Boost.Any does).
bjogio
bjogio
What?

ConfigUnsignedInt foo("foo", 0);
ConfigBool bar("foo", false);

std::cout << foo << bar;
foo = 3;
bar = false;
std::cout << foo << bar;

where is typecast?
Example of loading windows standard *.ini

//Load the configurationvoid ConfigurationSystem::Load(const char* FileName){GUARD	std::string file(FileName);	if(UserDef == false)	{		if(strcmp(FileName, UserFileName.c_str()) == 0)	//HACK "ugly"		{					UserDef = true;			return;		}	}	std::ifstream inifile(FileName, std::ios::in);	if(!inifile) EXCEPT( Exception::ExceptionInternal, "Problems occurred in" + file);	std::string Line, Section, Key, Value;	while (std::getline(inifile,Line))	{		//Format(Line);		if(Line != "")		{			if(Line.at(0) == '[' && Line[Line.length()-1]  == ']')			{				Section = Line;				Section = Line.substr(1, Line.length()-2);				if(Section.size() == 0)					EXCEPT( Exception::ExceptionInternal, file + " : ( " + Line +						" ) Section not specified");			} else			{				size_t x;				x = Line.find("=");				if(x == std::string::npos)					EXCEPT( Exception::ExceptionInternal, file + " : ( " + Line +						 " ) Key not specified");				Key = Line.substr(0, x);				Value = Line.substr(x + 1, Line.length());				if(Key.length() == 0 || Value.length() == 0)						EXCEPT( Exception::ExceptionInternal, file + " : ( " + Line +						" ) Key not specified");				AddValue(Section, Key, Value);			}		}	}	inifile.close();	++NumFile;UNGUARD(ClassName, "Load");}

My class ConfigurationSystem can hold or:

typedef std::map KeyMap;
KeyMap UserSettings;

or:

typedef std::map KeyMap;
KeyMap UserSettings;

but I prefer the first and getting data in this way:

void ConfigurationSystem::GetValue(const std::string& Section, ConfigValueBase* Cfg){	Cfg->StringToValue(GetValue(Section, Cfg->GetName()));	++NumAccess;}std::string ConfigurationSystem::GetValue(const std::string& Section, const std::string& Key){GUARD	bool found = false;	SectionMap* ToWatch = &UserSettings	SectionIterator = ToWatch->find(Section);	if(SectionIterator != (*ToWatch).end())	{		KeyIterator = (*SectionIterator).second.find(Key);		found = (KeyIterator != (*SectionIterator).second.end());	}	if(!found)	{		ToWatch = &ConstSettings		SectionIterator = ToWatch->find(Section);		if(SectionIterator != (*ToWatch).end())		{			KeyIterator = (*SectionIterator).second.find(Key);			found = (KeyIterator != (*SectionIterator).second.end());		}	}	if(!found)	{		std::string str("Key: " + Key + " Section: " + Section);		EXCEPT(Exception::ExceptionInternal, str);	}	return KeyIterator->second;UNGUARD(ClassName, "GetValue")}


cause in this way I separate the implementation of CVar for the Configuration pool. Bye.
[ILTUOMONDOFUTURO]
Oxyacetylene
Oxyacetylene
I use boost::any for my config system. Works fine.

The way mines works, is I have a base class for a configuration variable, that can hold any type (internally it uses boost::any, to store the value). Then I have classes that inherit from the base type, to implement different types.

If I want to access a configuration variable, I simply declare a configuration variable with the appropriate name, which automatically uses the config system to look up the right variable.

e.g.

Core::ConsoleBool dbg_disablemouse ( "dbg_disablemouse", false );if ( !dbg_disablemouse ){    AcquireMouse();}...Core::ConsoleFloat phys_friction ( "phys_friction", 0.04 );


What this does, is declare a boolean configuration variable. If a variable in the config file has been read in, which has the same name, then the config variable will get the value that was read from the config file. Otherwise, it will use the default value of false, passed to the ConsoleBool constructor.

If a variable was read from the config file, but it's of a different type, float for example, then the ConsoleBool will overwrite the value of the config variable with the default value of false.
bjogio
bjogio
Quote:
Original post by snk_kid
Quote:
Original post by bjogio
This is how I achieve this in my engine "iltuomondofuturo":....


That code has some "issues", like SetValue passing arguments by value and GetValue returning by value. It also uses manual type erasor as opposed to automatic type erasor which is much nicer (like Boost.Any does).


I do not like Boost.any. I do pass argument for value cause:

typedef ConfigVar ConfigInt;
typedef ConfigVar ConfigUnsignedInt;
typedef ConfigVar ConfigFloat;
typedef ConfigVar ConfigBool;
typedef ConfigVar ConfigString;

for the first 4 I do not have problems and for the last one I have a memcpy. Config var do not change a lot during program execution. When you run the first time, possibly during execution if the user change some settings and when you reset it all so is ok for me.
You can make SetValue virtual and change it in:

void SetValue(T& Val) { Value = Val; }

for std::string specialization (I can make it, uh yes I can, a stupid mistake), or change for everything.
As I said is a little tricky implementation but better than nothing :)
Do you see any other errors? I really like to improve my code when I can.
[ILTUOMONDOFUTURO]
random_thinker
random_thinker
Why don't you just load everything as a string, then use another class to interpret the string as you load it?

That way you can just do something like:

typedef map string_map;

I've developed an Echo class that echos the info in from or out to file according to the type. As is the case in Lisp, I interpret anything with a '+-0123456789.' as a float, and anything with just a '+-0123456789' as an int, and everything else is a string. There are excellent C++ Standard Library algorithms that can be used at the heart of this.

Now I can use very simple containers and basically one command 'readline(T &)' to read the data; it is interpreted automatically. Simple.

--random
--random_thinkerAs Albert Einstein said: 'Imagination is more important than knowledge'. Of course, he also said: 'If I had only known, I would have been a locksmith'.
snk_kid
snk_kid
Quote:
Original post by bjogio
for the first 4 I do not have problems


What you can do is use some template metaprogramming & type traits to either pass by value for built-in primitive types or by (constant) reference for all other types, i have code for that you can use:

#include <boost/mpl/eval_if.hpp>#include <boost/mpl/identity.hpp>#include <boost/type_traits/add_reference.hpp>#include <boost/type_traits/is_fundamental.hpp>template < typename Tq >struct param_type : boost::mpl::eval_if<    boost::is_fundamental<Tq>,     boost::mpl::identity<Tq>,    boost::add_reference<Tq const>> {};


usage:

template < typename Tp >struct foo {   void bar(typename param_type<Tp>::type);};


Quote:
Original post by bjogio
for the last one I have a memcpy.


I didn't see memcpy being used in the above code, if you mean you are using memcpy to copy to and from std::string this wont work correctly use it's copy constructor for that, You should only use memcpy for POD-types (POD == Plain old data type a technical term used in the C++ standard).

Quote:
Original post by bjogio
Config var do not change a lot during program execution...


I think a slight a confusion here, when i mentioned automatic type erasor i mean a C++ idiom, its kinda hard to explain without a code example.

Quote:
Original post by bjogio
Do you see any other errors? I really like to improve my code when I can.


I'll look into and post some improvements if you want me to.

Topic Locked

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

Sign in to reply to this topic.