Question


I am trying to recreate some classes from the C++ standard library in C. For example, the std::pair class.
To emulate templates, I used macros of course. Here is an example of how it looks like:

#define _TEMPLATE_PAIR_STRUCT(T1, T2, TNAME, STRNAME) \
typedef struct {                                  \
    T1* first;                                    \
    T2* second;                                   \
} STRNAME;

#define _TEMPLATE_PAIR_NEW(T1, T2, TNAME, STRNAME) \
    STRNAME* TNAME##_new()                         \
    {                                              \
        STRNAME *new = malloc(sizeof( STRNAME ));  \
        new->first = malloc(sizeof(T1));           \
        new->second = malloc(sizeof(T2));          \
        return new;                                \
    }

If I am trying to use this structure in multiple source files, I have to generate the code multiple times. That, obviously, leads to an error.

Is there a way to fix this, so I can use these "templates" in C?

Was it helpful?

Solution

As others have said, there are a few things to keep in mind, mainly make sure there is only one function definition.

I don't particularly like this solution but here it is.

One header to rule them all (pair.h)

#ifndef TEMPLATE_PAIR
#define TEMPLATE_PAIR

#include <stdlib.h>

#define PAIR_NAME( T1, T2 ) T1##T2##NAME
#define PAIR_PTR_NAME( T1, T2 ) T1##T2##PTR_NAME

#define PAIR_CREATE( T1, T2) MAKE##T1##T2
#define PAIR_PTR_CREATE( T1, T2) MAKE_PTR##T1##T2

#define PAIR_PTR_FREE(T1, T2) FREE##T1##T2

#define PAIR_DEFINITION( T1, T2) \
    typedef struct { \
    T1 first; \
    T2 second ; \
    } PAIR_NAME(T1, T2)

#define PAIR_PTR_DEFINITION( T1, T2) \
    typedef struct { \
    T1* first; \
    T2* second ; \
    } PAIR_PTR_NAME(T1, T2)

#define MAKE_PAIR_DECLARE(T1, T2) PAIR_NAME(T1, T2) PAIR_CREATE(T1, T2) ( const T1& V1, const T2& V2 )
#define MAKE_PAIR_PTR_DECLARE(T1, T2) PAIR_PTR_NAME(T1, T2) PAIR_PTR_CREATE(T1, T2) ( const T1& V1, const T2& V2 )
#define PAIR_PTR_FREE_DECLARE(T1, T2) void PAIR_PTR_FREE(T1, T2) ( PAIR_PTR_NAME(T1, T2) & Pair )

#define MAKE_PAIR_SIGNATURE(T1, T2) PAIR_NAME(T1, T2) PAIR_CREATE(T1, T2) ( const T1& V1, const T2& V2 )
#define MAKE_PAIR_PTR_SIGNATURE(T1, T2) PAIR_PTR_NAME(T1, T2) PAIR_PTR_CREATE(T1, T2) ( const T1& V1, const T2& V2 )

#define FREE_PAIR_PTR_SIGNATURE(T1, T2) void PAIR_PTR_FREE(T1, T2) ( PAIR_PTR_NAME(T1, T2) & Pair )

#define MAKE_PAIR_DEFINE( T1, T2 ) \
    MAKE_PAIR_SIGNATURE(T1, T2) { \
        PAIR_NAME(T1, T2) pair; \
        pair.first = V1; \
        pair.second = V2; \
        return pair; \
        }

#define MAKE_PAIR_PTR_DEFINE( T1, T2 ) \
    MAKE_PAIR_PTR_SIGNATURE(T1, T2) { \
        PAIR_PTR_NAME(T1, T2) pair; \
        pair.first = malloc( sizeof(T1) ); \
        if ( pair.first != 0 ) *(pair.first) = V1; \
        pair.second = malloc( sizeof( T2) ) ; \
        if ( pair.second != 0 ) *(pair.second) = V2; \
        return pair; \
        }

#define PAIR_PTR_FREE_DEFINE( T1, T2 ) \
    FREE_PAIR_PTR_SIGNATURE(T1, T2) { \
    free( Pair.first ); \
    free( Pair.second ); \
    }

#endif

One header to bring them all (defs.h):

#ifndef DEFS_HEADER
#define DEFS_HEADER

#include "pair.h"

typedef int* pInt;

PAIR_DEFINITION( int, int );
PAIR_DEFINITION( int, double );
PAIR_DEFINITION( double, double );
PAIR_DEFINITION( pInt, pInt );
PAIR_DEFINITION( float, int );

PAIR_PTR_DEFINITION( int, int );

MAKE_PAIR_DECLARE( int, int );
MAKE_PAIR_DECLARE( int, double );
MAKE_PAIR_DECLARE( double, double );
MAKE_PAIR_DECLARE( pInt, pInt );
MAKE_PAIR_DECLARE( float, int );

MAKE_PAIR_PTR_DECLARE( int, int );
PAIR_PTR_FREE_DECLARE( int, int );

#endif

And in the darkness bind them (impl.c):

#include "defs.h"

MAKE_PAIR_DEFINE( int, int );
MAKE_PAIR_DEFINE( int, double );
MAKE_PAIR_DEFINE( double, double );
MAKE_PAIR_DEFINE( pInt, pInt );

// manual "instantiation"
MAKE_PAIR_SIGNATURE( float, int )
{
    PAIR_NAME( float, int ) local;

    local.first = V1;
    local.second = V2;
    return local;
}

MAKE_PAIR_PTR_DEFINE( int, int );
PAIR_PTR_FREE_DEFINE( int, int );

In the land of main where the shadows lie:

#include "defs.h"


int main(void)
{
    PAIR_NAME(int, int) myPairInts;
    PAIR_NAME( double, double) myPairDoubles;
    PAIR_NAME( pInt, pInt) myPairPointers;
    PAIR_NAME( float, int) myPairOther;

    PAIR_PTR_NAME( int, int ) pairPtr;


    myPairInts = PAIR_CREATE( int, int ) (1, 2);
    myPairDoubles = PAIR_CREATE( double, double ) (5, 6.5);
    myPairPointers = PAIR_CREATE( pInt, pInt) ( 0, 0 );
    myPairOther = PAIR_CREATE( float, int) (1, 1);

    pairPtr = PAIR_PTR_CREATE(int, int) (1, 2 );

    PAIR_PTR_FREE(int, int) (pairPtr );


    return 0;
}

PAIR_NAME creates a structure containing values, PAIR_PTR_NAME contains pointers to values. PAIR_CREATE and PAIR_PTR_CREATE create values and fill the data inside the pair.

You will need to define all the necessary combinations in "impl.c". Any compilation unit can #include "defs.h" and use the pairs.

EDIT - Answers to questions:

  1. "Wouldn't this cause trouble when I use this once in a library or something of that kind and then again in a program which uses both that library and the pair "template"?"

"pair.h" contains only macros, it can be safely used in any library or program.

What's important is that you don't define the same function twice. I wouldn't define the same structure twice either.

You can do the following though: - take pair.h, defs.h and impl.c as they are above and build them into a library (add any necessary __declspec(dllexport) and __declspec(dllimport) - you can then #include pair.h and defs.h and use the pairs defined there in a program - if you want to use new pairs, say for (float, float) you will need to add a new defs_my_program.h and a new impl_my_program.c to declare and define these new pair. The defs_my_program.h header can be included along with the defs.h header the library provides.

You still get one declaration and one definition for each pair. The only "downside" is that you can't really (safely) use pairs on-the-fly, they need to be centralized.

  1. "The way you chose the type and function names to be does bring some issues with it. You split up a pair with two values and one with two pointers. You would also need a specialisation for a pair with one pointer and one value, and for a pair with one value and one pointer. So I would have 4 cases of pairs already. Using this for triplets or even higher tuples, I would have to implement 2^n cases."

Well, for starters, you asked for std::pair and not for a touple.

Note that std::pair is the equivalent of PAIR_NAME, there is no std::pair that allocates dynamic memory.

You don't need any new specializations if you don't need to automatically use malloc. The example with pInt shows that you can create s pair of (int, int*) or (int*, int). It's just that the value of the pointer needs to come from outside the pair.

If you really want a pair of (int, int*) that automatically allocates memory for the int* you will have to add it yourself if you actually need it. I hope you will not.

For tuples of values, a not very optimal solution could be to use a pair of a pair and another element. This would give you a structure containing three elements. Something like that can probably be done via macro magics, cutting down on that exponential growth you are worried about.

OTHER TIPS

Declaring the struct multiple time is not a problem.

Defining the function multiple time is a problem. If you make the function static then it becomes a once per file issue. Even in C++ sometimes people will explicitly instantiate templates. You can have one file that has all the new functions.

If you want an automatic solution, then you need to look at how C++ compilers instantiate templates. Here is one method that was used ages ago.

  • Compile and link your code with no uses of PAIR_NEW
  • You will get undefined _new() functions.
  • Run a script that generates a C file with the proper PAIR_NEW() macro calls to define the undefined symbols
  • compile this new file and relink your project including the new file.

Abstracting from the question if it is a good idea, you have several things wrong in your implementation.

  • never hide a ; inside a macro, this is completely against visual expectations at the place this would be used.
  • other than in C++, a function that doesn't receive a parameter should have void inside the ().
  • names starting with an underscore and a capital letter are reserved for the C implementation in all contexts. Chose a better naming convention.
  • other than in C++ your new function wouldn't initialize the data, this is a bad naming convention, too.
  • you are using the identifier new as a local variable, bad if you want to interface this to some C++ one day
  • your macro for the function should split into three parts: (1) for the naming convention that plugs together the name of the function, (2) an inline definition and (3) an external "instantion" that lets you generate the function symbol in exactly one compilation unit.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top