views:

90

answers:

5

Alright, I have library I wrote in C that reads a file and provides access to its' data. The data is typed, so I'm using void pointer and a few accessor functions:

typedef struct nbt_tag
{
    nbt_type type; /* Type of the value */
    char *name;    /* tag name */

    void *value;   /* value to be casted to the corresponding type */

} nbt_tag;

int64_t *nbt_cast_long(nbt_tag *t)
{
    if (t->type != TAG_LONG) return NULL;

    return (int64_t *)t->value;
}

For different types (built-ins: TAG_BYTE (char), TAG_SHORT (int16_t), TAG_INT (int32_t), TAG_LONG (int64_t), TAG_FLOAT (float), TAG_DOUBLE (double), TAG_STRING (char *) and a few slightly more complex data types, TAG_List (struct nbt_list), TAG_COMPOUND (struct nbt_compound), TAG_BYTE_ARRAY (struct nbt_byte_array).

I'm now trying to map this to C++ in an elegant fashion but I can't get it done...

char getByte();                     // TAG_BYTE
int16_t getShort();                 // TAG_SHORT
int32_t getInt();                   // TAG_INT
int64_t getLong();                  // TAG_LONG
float getFloat();                   // TAG_FLOAT
double getDouble();                 // TAG_DOUBLE
std::string getString();            // TAG_STRING
std::vector<char> getByteArray();   // TAG_BYTE_ARRAY
std::vector<Tag> getCompound();     // TAG_COMPOUND

This feels way too verbose.. any better way?

A: 

You could template the function.

template <typename T>
typename T get();
Paul
A: 

Not really a direct answer, but take a look at VARIANT (in Windows), and the corresponding CComVariant and _variant_t wrapper classes: this does basically the same thing, and you might be able to get some insight from how it's done there.

I have done stuff like template casts, where the complexity is in the object, but it's easy to use, but YMMV.

Nick
Variant (and CComVariant) must be avoided like the devil.
Alexandre C.
Also, I'm on linux and I try to maintain some operating system independency :)
LukeN
A: 

There are certainly ways to use template voodoo to to reduce it all to a get<X>() function (with possibly a few specializations). But there you trade off the simplicity of writing those getX functions for probably having to write something like a small type-traits infrastructure for the template mechanism and/or some template specializations.

If the number of different types supported is never (or very rarely ever) going to change, then the simplest and most easily understandable might just be those various getX functions. It may be verbose, but those functions ought to be very quick to write and debug.

On the other hand, if you're comfortable with templates you could certainly try that approach.

TheUndeadFish
+3  A: 

This can do the job:

template <int> struct TypeTag {};
template <> struct TypeTag<TAG_BYTE> { typedef char type; };
// ...
template <> struct TypeTag<TAG_COMPOUND> { typedef vector<Tag> type; };

template <int tag>
typename TypeTag<tag>::type getValue(nbt_tab* t)
{
    if (t->type != tag) ... // throw an exception
    return *reinterpret_cast<typename TypeTag<tag>::type*>(t-value);
}

and use it like this:

char x = getValue<TAG_BYTE>(t);
vector<Tag> v = getValue<TAG_COMPOUND>(t);

You may want to add specializations like

template <>
vector<Tag> getValue<TAG_COMPOUND>(nbt_tab* t)
{
    if (t->type != TAG_COMPOUND) ... // throw something
    vector<Tag> ans(/* size */); // [rely on named return value optimization]

    // Fill the vector with your data

    return ans; // [Pray for the copy constructor not to get called]
}
Alexandre C.
This looks really promising, thanks! But will it work when `getValue()` is a class member? I forgot to add that to the question, all those functions are part of a class `Tag`
LukeN
@LukeN: Yep it will. The TypeTag classes can be either member classes of Tag or external to class Tag (I'd make them private member classes of Tag personally).
Alexandre C.
@LukeN: Note that you have in this case to declare the specializations *outside* the class, as `template<> vector<Tag> Tag::getValue<TAG_COMPOUND>() {...}`.
Alexandre C.
This has proven to be a really great solution in practice and it has taught me quite a bit about templates, too! Thanks very much, Alexandre :)
LukeN
@LukeN: you're welcome, glad I could help.
Alexandre C.
A: 

You can use a boost::variant. This type can store any of it's template arguments and can be queried for which it contains. Simply hold a bunch of them in a vector and then return a reference to your favourite variant.

DeadMG