how to return different types?

Started by
6 comments, last by rip-off 12 years, 1 month ago
does anyone have any recommendations or advice on how i would do the following. Pretty much i want a function to possibly return 2 different types of structs depending on the XML it is parsing


struct Error
{
string errorMessage;
string otherinfo;

}

struct Person
{
string firstname;
string lastname;
int age;
float height;
}

Error/Person ParseXML(string XML)
{
if (<person>)
return parsePerson(XML); // return a Error object
else
return parseError(XML); // return a person object

}
Advertisement
You probably want to switch to inherited classes. Either way, you're going to need to return a pointer instead of the struct itself. First, you'll probably want some way to tell which one is returned, so you should add an integer called type or something first in your structs. Then, make a third struct, called simply XMLReturn or something, that contains only the type variable. Have your function return a pointer of that type, and then just, return whichever struct you allocate normally. You can read the type of your XMLReturn variable and decide what to cast it back to.
struct typea
int type
String something

Struct typeb
int type
float somenumber

Struct basetype
int type

basetype* function
if something
typea *ret=malloc(typea) ;
ret.type=1
return ret


basetype *xml=function ()
if xml.type=1
typea *a=(typea*) xml
That looks incredibly messy and like the equivalent of constantly abusing dynamic_casts in C++ to check types. It seems like conceptual nonsense to be "deriving" unrelated things like Person and Errors from one base and introduce the mess of dynamically allocating/deallocating objects (especially after all the wild brute force casting) to implement the misguided idea of a single function returning completely different types of objects. Sometimes the question isn't "how can I do this", but "why on earth would I WANT to do this".

Either do what tons of C libraries do and have functions always return an error code while using an output parameter for your data, which will look like this


Person person;
Error error = parseXML(xml, &person);



or return a (well defined) dummy Person on error and set a global error object (C typically does both, with functions returning -1 on error and additionally setting the global error number, a completely separate function will then map error codes to error strings). Of course a global error has other problems of its own (not thread safe, the next call might overwrite your previous error, etc.)

In C++ you could use exceptions for errors, which makes some things very convenient (if your calls go through several levels, you don't have a dozen places to do error checking), but also a little messy (you need to learn writing exception safe code and all the try/catch can go a little out of hand).
f@dzhttp://festini.device-zero.de
This is all im trying to do:
1.) Have a function that can detect and effectively parse 2 diffrent possible XML messages. Upon return the XML we be stored in the appropriate struct and returned by the function. I can then have the caller somehow know the type that was returned and handle the struct according to its type. Here is my code if this helps



QList<CError> ParseError(QXmlStreamReader& streamReader)
{
QList<CError> errorList;
CError errorBuffer;

streamReader.readNext();
while (!(streamReader.tokenType() == QXmlStreamReader::EndElement && streamReader.name() == "ErrorMessage")) {
if (streamReader.tokenType() == QXmlStreamReader::StartElement) {
if (streamReader.name() == "Title") {
errorBuffer.strTitle = streamReader.readElementText();
}
if (streamReader.name() == "Message") {
errorBuffer.strMessage = streamReader.readElementText();
}
}
streamReader.readNext();
}
errorList.append(errorBuffer);
return errorList;
}

QList<CSubscription> ParseSubscription(QXmlStreamReader& streamReader)
{
QList<CSubscription> subscriptionList;
CSubscription subscriptionBuffer;

streamReader.readNext();
while (streamReader.tokenType() != QXmlStreamReader::EndDocument) {
while (!(streamReader.tokenType() == QXmlStreamReader::EndElement && streamReader.name() == "Subscription")) {
if (streamReader.tokenType() == QXmlStreamReader::StartElement) {
if (streamReader.name() == "NewestVersion") {
subscriptionBuffer.strNewestVersion = streamReader.readElementText();
}
if (streamReader.name() == "ProductName") {
subscriptionBuffer.strGame = streamReader.readElementText();
}
if (streamReader.name() == "ExpiryDate") {
subscriptionBuffer.strExpireDate = streamReader.readElementText();
}
if (streamReader.name() == "Detection") {
subscriptionBuffer.strDetection = streamReader.readElementText();
}
}
streamReader.readNext();
}
subscriptionList.append(subscriptionBuffer);
}
return subscriptionList;
}


QList<EITHERSTRUCT> ParseXML(QString& strXML)
{
QXmlStreamReader streamReader(strXML);

while (!streamReader.atEnd()) {
QXmlStreamReader::TokenType token = streamReader.readNext();
if(token == QXmlStreamReader::StartDocument) {
continue;
}
if(token == QXmlStreamReader::StartElement) {
if (streamReader.name() == "ErrorMessage") {
return ParseError(streamReader);
}
if (streamReader.name() == "Subscription") {
return ParseSubscription(streamReader);
}
}
}
return SOME_ERROR;
}
Make a function to get the type of the data and then call geterror or getplayer, whichever is the right one, passing them the xml already parsed by the gettype thing hopefully (figure out better names for them)

o3o

What you are after is a factory pattern and most of those in C++ require RTTI in some way or other. I use class registrars to collect the creation functions at compile time to create objects from a factory http://code.google.c...sRegistration.h, http://code.google.com/p/dxengine/source/browse/trunk/src/RTTI.h, http://code.google.com/p/dxengine/source/browse/trunk/src/RTTIMacros.inl. But I think you can do this with a templated factory as well http://stackoverflow...ry-pattern-in-c

Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, theHunter, theHunter: Primal, Mad Max, Watch Dogs: Legion

One idea is to return a tuple of optional Person, Error:

template<typename T>
struct ParseResult {
boost::optional<T> success;
boost::optional<Error> error;

ParseResult(const T &success) {
// ...
}

ParseResult(const Error &error) {
// ...
}
};

The caller can inspect the optional values to determine which value was returned. A more generalised version of this is boost::variant, or in extreme cases boost::any. I'm not aware of these types being included in C++0x.

If the file contains many objects, the above could be two containers:


template<typename T>
struct ParseResults {
std::vector<T> successes;
std::vector<Error> errors;
};

If the order is important (e.g. parsing a list of elements and you want to correlate errors with particular entries) then you can store the index in the vectors too.

This topic is closed to new replies.

Advertisement