1

After some work on the generic vector I asked about on this question, I would like to know if there is any way of checking that each instanciation of the library is only done once per type.

Here is what the current header file looks like:

#ifndef VECTOR_GENERIC_MACROS
#define VECTOR_GENERIC_MACROS

    #ifndef TOKENPASTE
    #define TOKENPASTE(a, b) a ## b
    #endif

    #define vector_t(T) TOKENPASTE(vector_t_, T)

    #define vector_at(T) TOKENPASTE(*vector_at_, T)

    #define vector_init(T) TOKENPASTE(vector_init_, T)
    #define vector_destroy(T) TOKENPASTE(vector_destroy_, T)
    #define vector_new(T) TOKENPASTE(vector_new_, T)
    #define vector_delete(T) TOKENPASTE(vector_delete_, T)

    #define vector_push_back(T) TOKENPASTE(vector_push_back_, T)
    #define vector_pop_back(T) TOKENPASTE(vector_pop_back_, T)
    #define vector_resize(T) TOKENPASTE(vector_resize_, T)
    #define vector_reserve(T) TOKENPASTE(vector_reserve_, T)

#endif

typedef struct {
    size_t size;
    size_t capacity;
    TYPE *data;
} vector_t(TYPE);

inline TYPE vector_at(TYPE)(vector_t(TYPE) *vector, size_t pos);

void vector_init(TYPE)(vector_t(TYPE) *vector, size_t size);
void vector_destroy(TYPE)(vector_t(TYPE) *vector);
inline TYPE *vector_new(TYPE)(size_t size);
inline void vector_delete(TYPE)(vector_t(TYPE) *vector);

void vector_push_back(TYPE)(vector_t(TYPE) *vector, TYPE value);
inline TYPE vector_pop_back(TYPE)(vector_t(TYPE) *vector);
inline void vector_resize(TYPE)(vector_t(TYPE) *vector, size_t size);
void vector_reserve(TYPE)(vector_t(TYPE) *vector, size_t size);

The header can then be included along with the source definitions:

#include <stdio.h>

#define TYPE int
#include "vector.h"
#include "vector.def"
#undef TYPE

int main()
{
    vector_t(int) myVectorInt;
    vector_init(int)(&myVectorInt, 0);

    for (int i = 0; i < 10; ++i)
        vector_push_back(int)(&myVectorInt, i);

    for (int i = 0; i < myVectorInt.size; ++i)
        printf("%d ", ++vector_at(int)(&myVectorInt, i));

    vector_destroy(int)(&myVectorInt);
    return 0;
}

I would like to make sure that the content below that last endif is only included once per TYPE.

Obviously, #ifdef VECTOR_INSTANCE(TYPE) does not work, so I'm really out of ideas...

Community
  • 1
  • 1
Siapran
  • 65
  • 6
  • 1
    I'm confused, don't you already find this out from a multiple-definition error for the typedef? What is your goal? Tell you the definition was used twice and stop compilation, tell you and keep going, or silently ignore multiple definition? – Ben Voigt Mar 14 '15 at 18:59
  • the goal would be to silently ignore multiple definitions, so that the programmer may include the library safely from multiple files. http://en.wikipedia.org/wiki/Include_guard – Siapran Mar 14 '15 at 19:02

2 Answers2

2

It's a though question, however, I was also interested in the matter when I asked a similar question to yours some time ago.

My conclusions is that if you are going to use vectors (or, using more accurate naming, dynamic arrays) of many different types then it's wasteful to have all those functions vector_##TYPE##_reserve(), vector_##type##_resize(), etc... multiple times.

Instead, it is more efficient and clean to have those functions defined only once in a separate .c file, using your type's size as an extra argument. Those functions prototyped in a separate .h file. Then the same .h file would provide macros that generate functions wrappers for your own types, so that you don't see it using the size as an extra argument.

For example, your vector.h header would contain the following :

/* Declare functions operating on a generic vector type */
void vector_generic_resize(void *vector, size_t size, size_t data_size);
void vector_generic_push_back(void *vector, void *value, size_t data_size);
void *vector_generic_pop_back(void *vector, size_t data_size);
void vector_generic_init(void *vector, size_t size, size_t data_size);
void vector_generic_destroy(void *vector) ; // I don't think data_size is needed here

/* Taken from the example in the question */
#define VECTOR_DEFINITION(type)\
typedef struct {\
    size_t size;\
    size_t capacity;\
    type *data;\
} vector_ ## type ## _t;\

/* Declare wrapper macros to make the above functions usable */
/* First the easy ones */
#define vector_resize(vector, size) vector_generic_resize(vector, size, sizeof(vector.data[0]))
#define vector_init(vector, size) vector_generic_init(vector, size, sizeof(vector.data[0]))
/* Type has to be given as an argument for the cast operator */
#define vector_pop_back(vector, type) (*(type*)(vector_generic_pop_back(vector, sizeof(vector.data[0]))))

/* This one is tricky, if 'value' is a constant, it's address cannot be taken.
I don't know if any better workarround is possible. */
#define vector_push_const(vector, type, value)                    \
{                                                                 \
    type temp = value;                                         \
    vector_generic_push_back(vector, &temp, sizeof(vector.data[0]));\
}

/* Equivalent macro, but for pushing variables instead of constants */
#define vector_push_var(vector, value) vector_generic_push_back(vector, &value, sizeof(vector.data[0]))

/* Super-macro rediriging to constant or variable version of push_back depending on the context */
#define GET_MACRO(_1,_2,_3,NAME,...) NAME
#define vector_push_back(...) GET_MACRO(__VA_ARGS__, vector_push_const, vector_push_var)(__VA_ARGS__)

/* This macro isn't really needed, but just for homogenity */
#define vector_descroy(vector) vector_generic_destroy(vector)

The functions can then be used as you said in the example you linked, with the significant exception of vector_generic_push_back where unfortunately the type has to be specified each time as an extra macro argument.

So with this solution

  • You only have to do VECTOR_DEFINITION() within the .c file, avoiding the risk of declaring it with the same type twice
  • The vector library is only existing once in the binary
  • The macros can be used elegantly without using the type in their names, except for the pop back macro and the push literal macro.
  • If this is a problem you could make the push literal use long long always, it will work but potentially loose efficiency.
  • Similarly you could make the pop_back() macro and the vector_generic_pop_back() functions not return anything like they does in the C++ language, so that if you do both of those tricks you never need to use the type name explicitly in the macros.

As a reference, the main function you posted in the example that is linked in your question has to be adapted like that :

    #include <stdio.h>
    #include <stdlib.h>
    #include "vector.h"

    typedef unsigned int uint;
    typedef char* str;

    VECTOR_DEFINITION(uint)
    VECTOR_DEFINITION(str)

    int main()
    {
        vector_uint_t vector;
        vector_init(&vector, 10);

        for (unsigned int i = 0; i < vector.size; ++i)
            vector.data[i] = i;

        for (unsigned int i = 0; i < 10; ++i)
            vector_push_back(&vector, i);

        /* When pushing back a constant, we *have* to specity the type */
        /* It is OK to use C keywords as they are supressed by the preprocessor */
        vector_push_back(&vector, unsigned int, 12);

        for (unsigned int i = 0; i < vector.size; ++i)
            printf("%d ", vector.data[i]);
        printf("\n");

        vector_destroy(&vector);

        vector_str_t sentence;
        vector_init(&sentence, 0);

        vector_push_back(&sentence, "Hello");
        vector_push_back(&sentence, str, "World!");  /* Also possible, less efficient */
        vector_push_back(&sentence, "How");
        vector_push_back(&sentence, "are");
        vector_push_back(&sentence, "you?");

        for (unsigned int i = 0; i < sentence.size; ++i)
            printf("%s ", sentence.data[i]);
        printf("\n");

        vector_destroy(&sentence);

        return 0;
    }
Community
  • 1
  • 1
Bregalad
  • 634
  • 7
  • 15
  • You could fix the issue of requiring the type explicitely in `vector_push_back` by having 2 different macros for constant values (requiring type) and for already defined variables (not requiring type). This could be done with [macro overloading](http://stackoverflow.com/questions/11761703/overloading-macro-on-number-of-arguments), but I couldn't get it working personally so I'd rather not include that in the answer. – Bregalad Mar 14 '15 at 21:02
  • 1
    Ok I got the macro overloading working, I'll edit my post to show that. – Bregalad Mar 14 '15 at 22:08
  • This is an interesting approach to the problem. Not strictly an answer to my question, but good enough. Initially, the aim was to implement other containers such as maps, queues, etc. I'll see if I can get this kind of workaround to work everywhere, but I'm afraid I'll have to keep my version for coherency between the containers. Thank you for the answer and the research! – Siapran Mar 14 '15 at 22:35
  • You're welcome :) And yeah, getting maps to work in such a transparent way would be much thougher. You'd need at least the size of both types and a pointer to a comparison function between two key types to be here at all times. Or you could do it with a hash map, which performs worse for small maps but better for large maps. Anyway it's very interesting, if you ever get this working I'd be interested :) – Bregalad Mar 15 '15 at 08:48
  • Also I forgot to mention but in the vector functions themselves, you need to work with a vector of `void`, and always use memcpy() instead of assignement, using the size as an extra argument. – Bregalad Mar 15 '15 at 08:55
0

suggest:

remove the prototypes from the vector.h file.

place the prototypes at the top of the vector.def file.

remove the typedef struct from the vector.h file

place the typedef struct before the prototypes in the vector.def file.

then multiples #include statements for the vector.h file will have no bad effects.

Then use the following, in each source file that is to use these vector types:

#include<vector.h>

#define TYPE int
#include<vector.def>
#undef TYPE

#define TYPE char
#include<vector.def>
#undef TYPE
... etc


BTW:
There is no library involved, so I'm a bit confused by the reference 
to 'library' in the question

It may be worthwhile to also prefix the 'static' modifier 
to each of the function definitions so the definitions are 
not visible across source files

It may be worthwhile to use parens around the parameters to TOKENPASTE
so modifiers like 'static' and.or 'const' 
can be prefixed to the function names.
user3629249
  • 16,402
  • 1
  • 16
  • 17