mupuf.org // we are octopimupuf.org

CVariant, a C-container That Can Handle Strings, Int and Floats

Would you fancy a C-container that can hold a string, an integer or a float ?

There are some cases where you need, for instance, to return a value that could be of any type. This case just happened to me when I needed to handle parameters for PPassKeeper modules.

In this article, I will explain a solution to solve this problem from the basics to the final code I did.

The basic technique for creating multi-type containers

The simplest solution

To create a multi-type container, you need to store 2 things:

  • The type: The type of the data we want to store
  • The data: What we want to store

This can be done using a simple struct and an enum:

typedef enum {c_string, c_int, c_float } data_type_t ;
typedef struct
{
    data_type_t type;
    void* data;
}container;

To initialize it, you would do :

container my_string {c_string, (void*)"a string"};
container my_int {c_int, (void*)42};

And to read the value, you would do :

container var{ ...., .... };
switch(var.type)
{
    case c_string:
        printf("Value is %s\n", (char*)var.value);
        break;
    case c_int:
        printf("Value is %i\n", (int)var.value);
        break;
    ...
};

This solution works but we have to cast the value any time we want to set or read data from the container.

Better solution : The use of unions

In this solution, we will get rid of the cast we needed in the previous solution. To do so, we will need the compiler to do some checks for us at compilation time. Sounds good isn’t it ?

we will use (follow the link if you can’t remember the use of union).

As we know, unions are a way to do polymorphism in C/C++. The problem is that it doesn’t store the type of the data stored. We will address this problem using the same pattern we used in the first solution :)

So, let’s use an union now instead of a void* to store data:

typedef enum {c_string, c_int, c_float } data_type_t ;
typedef struct
{
    data_type_t type;
    union{char* str_v, int int_v, float float_v };
}container;

To initialize it, you would do :

container my_string;
my_string.type=c_string;
my_string.int_v="a string";

container my_int;
my_int.type=c_int;
my_int.str_v=42;

And to read the value, you would do :

container var{ ...., .... };
switch(var.type)
{
    case c_string:
        printf("Value is %s\n", var.str_v);
        break;
    case c_int:
        printf("Value is %i\n", var.int_v);
        break;
    ...
};

OK, we got rid of the casts but the initialization takes 3 times more lines !! That’s true, but it is not really a problem as we are about to do an Abstract Type from it ;)

Create an Abstract Type

Now we have a proper solution to store type-variant data, we need to create an Abstract Type for it. This means we will create function to construct the container, to access it and to free it.

The use of Abstract Types are important because it allows you to change the internal structure of your container without touching anything else. This also helps to get a much more understandable code for people maintaining your program as they won’t need to read any header, your code would already be self explanatory.

My solution

Now we went through a theoretic solution to create type-variant containers, I’ll show you my solution.

The code is release under LGPL 2.0 or later, it means you can reuse it even in proprietary software. You’ll just need to keep the header and to send me your modifications that I may add to the article in return.

I decided not to create a shared library because of the small amount of code in there. It would add unnecessary complexity to your building system as it would add an extra dependency.

What do you think of this solution ? Feel free to leave comments.

Comments