C++ STL container mulitple types

Started by
15 comments, last by random_thinker 16 years, 8 months ago
This issue may have come up before, but I have not found any answers to it yet. In the STL, containers the type is fixed for every element. I would like to have containers with multiple types. I have seen on Sourceforge a discussion of a class identified as 'MultiType' which is basically morphs into various primitive types and a few others, such as 'std::string'. This allows such things as 'std::map<std::string,MultiType>'. My question is, what are the opinions as to this approach (as it could be argued that it breaks the type safety of C++ and I suppose could lead to possible pointer memory allocation problems), and secondly, are there other approaches? Additionally, I have tried using 'std::string' as a base type and converting to primitives using a stringstream-type lexical_cast (with std::fixed << std::setprecision(20)), but I have discovered discrepancies as compared to the same calculations using native types (about 0.000006% which is not acceptable for my work). I have assumed these are round-off related when using this conversion for primitive types; hence the desire to keep numeric data in its primitive form. Any ideas?
--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'.
Advertisement
Which types are you considering storing? The best course of action will depend heavily on that.
boost::any
cdiggins::any
boost::variant

Use google.

Keep in mind, that this is merely type-casting. It is not possible, due to language design, to morph a type into another type, unless the conversion between the two is explicitly defined.

While stringstream may seem similar to string, they are completely unrelated. The only reason they can inter-operate is because they are explicitly supported.

Having fixed type in a container is A Good Thing(tm). So is type-safety. If inheritance and polymorphism doesn't give you adequate solution, chances are you're deliberately trying to break something.
Hi Si,

I've got std::strings, single, double and various int types that could be used. I'm implementing a simple lisp-style parser for calculated fields in a report template system, so I need to be able to use std::strings as keys for map values as well as std::strings or primitives.

I have implemented this quite well by using the STL std::vector<std::string> and STL std::map<std::string,std::string> and converting types back and forth with a lexical_cast<primitiveType> approach, but now I have discovered this numerical difference from the calculation in native form. Hence my thinking about something like MultiType.
--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'.
Hi Antheus,

I couldn't agree with you more, but I'm stumped on this problem. I'm trying to minimise the number of underlying data containers for this application (always using KISS), and I realise that one of the fundamental strengths of C and C++ is strict typing. But still I have this problem, that I would like to solve without breaking the type safety of the language.
--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'.
It sounds like you'd best benefit from using a discriminated union solution like boost::variant. However, consider also embedding an existing scripting solution and exposing your programs data to the scripting language.
Thanks everyone, I'm checking these sources further. What I'm specifically doing is this:

"This is some text from a report with a calculated field that results in the number: ~12,L,2,calculated.value."

;;calculated.value
(* 5.67 7.45 (/ mapkey.value 9)(+ mapkey2.value 4))

The system that I have written works very well; the above string replaces the substring '~12,L,2,calculated.value' with the formatted result of ';;calculated.value', and 'calculated.value' uses the strings 'mapkey.value' and 'mapkey2.value' to access a std::map pair<std::string,std::string> and find the associated values, and convert them to primitive types. But all the data is first read as a string, then parsed, and the integers, numbers and strings (keys) are interpreted. After conversions, however, this small error occurs, which is not acceptable. My problem is that, in some cases the map values are primitives, and in some cases the map values are strings.

Another option is to create another std::map<std::string,double>, for example, and store numeric data in that, but this increases container complexity and management significantly. Under this approach I would need four or five std::maps<>, and have access routines for each.
--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'.
Quote:Original post by random_thinker
This issue may have come up before, but I have not found any answers to it yet. In the STL, containers the type is fixed for every element. I would like to have containers with multiple types.
<...>


The reason why STL containers uses fixed type elements is that these elements must be of the same size to make access to elements easier. For example, if you put values of constant size into array, you get simple layout with elements accessible by index. To access certain element in array of variable-sized elements is possible by byte-index only which might be hard to compute.

I suggest you to either use pointers - allocate elements on heap and insert them into container (pointers are constant-sized), or create union of types you are going to use.
Quote:After conversions, however, this small error occurs, which is not acceptable. My problem is that, in some cases the map values are primitives, and in some cases the map values are strings.


C++ gives you guarantees on accuracy of floating point calculations. Even without conversion to string, your accuracy may suffer.
Hi Again All,

Thanks for the excellent info...still formulating an approach to this...to keep things simple and to play on some of the ideas above, what about something like (this pseudocode has not been tested!):

// Consider each parsed element as an atom that is either number or text.
struct atom
{
double number;
std::string text;
};

// Does the string resemble a number?
bool isNumber(const std::string & strg)
{ return (strg.find_first_not_of(<number_string>) == std::string::npos); }

// Make a new atom from a string at initial parsing.
atom * makeAtom(const std::string & strg)
{
atom * pAtom;
if(isNumber(strg))
{
pAtom->number = lexical_cast<double>(strg);
pAtom->text = "";
}
else
{
pAtom->number = 0;
pAtom->text = strg;
}
return pAtom; // on exit, content is returned and pAtom is destroyed.
}

// Convenience typedefs allowing dynamic allocation.
typedef std::map<std::string,atom*> atom_map;
typedef std::vector<atom_map*> atom_map_vec;

// Create an atom_map_vec.
atom_map_vec = amvec;

// Load first atom_map.
amvec.push_back(new atom_map);

// Parse a key-value string and return them.
std::string key = "string_key";
std::string value = "123.789";

// Insert a key-value pair.
amvec[0]->insert(std::make_pair(key,(makeAtom(value))));

The above should allow some form of dynamic allocation, and when each map pair is used the 'non-null' values only would be accessed. This also preserves type safety and because all containers are iterating across pointers the efficiency should be preserved. However, it still does not seem the best solution to me.

Any suggestions or ideas?

--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'.

This topic is closed to new replies.

Advertisement