A "mostly failed" attempt at bringing class-like functionality to C99/C11.
Reese is a library for C99/C11 that introduces a pseudo-class-like structure inside of the C language. Although, the syntax looks far from a native class implementation, there is only so much that can be done using the preprocessor. As such, care has been taken to keep the syntax as concise and clean as possible.
Follow these steps to install reese using CMake:
- Open the command line in the directory where you would like to install reese.
- Fetch the repo using git:
git clone https://www.github.com/reacfen/reese
- A directory called
reese
will be created. Now, enter that directory usingcd
:cd ./reese
- Create the build directory where the library will be built:
mkdir build
- Enter the newly created
build
directory:cd ./build
- Run
cmake
:cmake ..
- Now run
make
to build it:make
- After the command finishes, you will be able to find the compiled library in the
build
directory.
And that's it. You are ready to start using reese in your project!
IMPORTANT: Make sure to include the header
<reese.h>
if you are going to use any of reese's functionality!
Using reese is very straightforward and simple. A simple example of a "class" definition would look like this:
// Defining a class called "my_class"
REESE_DEFINE_CLASS_INLINE(my_class,
// Instance variable(s)
int x;
, // <- This comma is important!
// Define the constructor (You must define it before all other member functions!)
my_class, (int x), {
self->x = x;
},
// Define the member method(s) (Can be defined in any order you want)
set_x, (int x), {
self->x = x;
},
get_x, (void), {
/* Note that you have to use 'reese_return' and NOT 'return' when returning a value
* from a member function! */
reese_return(self->x);
}
)
This is called an inline class definition where the constructor and all member functions are defined inside the class definition itself.
Note: Due to the nature of preprocessor macros, all separate components of a function i.e., the function name, the parameters and the body, need to be separated using a comma (,).
Therefore, something like
my_function(int foo) {}
needs to be written asmy_function, (int foo), {}
inside of the class definition.
Alternatively, you can also define the member methods outside of the class like this:
// Definition of "my_class"
REESE_DEFINE_CLASS(my_class,
// Instance variable(s)
int x;
, // <- Again, don't forget this comma!
// Declare the constructor
my_class, (int x),
// Declare the member method(s)
set_x, (int x),
get_x, (void)
)
// Now you can define them separately outside the class!
REESE_DEFINE_DRAFT(
// Define the constructor
my_class, (int x), {
self->x = x;
},
// Define the member method(s)
set_x, (int x), {
self->x = x;
},
get_x, (void), {
reese_return(self->x);
}
)
This is especially useful if you want to declare a class inside of a C header and define it inside of a C source file.
Note: There are variety of ways to define a class and its member methods using reese. However, the above two ways are the most straightforward and simple.
You can reference classes that are not yet defined by taking a pointer to it:
typedef struct example {
// "my_class" is not defined yet, so you must take a pointer to it for now.
struct my_class *instance;
} example;
// Defining a class called "my_class"
REESE_DEFINE_CLASS_INLINE(my_class,
// Instance variable(s)
int x;
,
// Constructor and member functions
my_class, (int x), {
self->x = x;
},
set_x, (int x), {
self->x = x;
},
get_x, (void), {
reese_return(self->x);
}
)
int main(void) {
// ...
example example_inst;
my_class inst = create_my_class(123);
example_inst.instance = &inst;
printf("%d\n", ret_cast(int)capture(my_class, example_inst.instance).get_x().ret());
// ...
finish_capture();
// ...
}
Creating an instance of a class using reese requires a bit getting used to, but is also quite straightforward. You can create an instance of a class using three different ways:
- Using
create_{class_name}()
:my_class inst = create_my_class(123);
- Using
REESE_CONSTRUCT()
:my_class inst = REESE_CONSTUCT(my_class, 123);
- Using
REESE_FETCH_CONSTRUCTOR()
:Once you have created the instance, you can call its class's methods like this:my_class inst = REESE_FETCH_CONSTRUCTOR(my_class) (123);
// ...
// To fetch a returned value from a member function returning non-void
const int returned_value = ret_cast(int)capture(my_class, inst).get_x().ret();
printf("%d\n", returned_value); // 123
// If you want to simply invoke a function and do not care about what it returns
capture(my_class, inst).set_x(321);
printf("%d\n", inst.x); // 321
// ...
finish_capture();
Note: If you are using the GCC compiler, you may use the
typeof(inst)
compiler extension to automatically deduce the type of the instance. Hence the above code can be shortened to:#define cap(instance) capture(typeof(instance), instance) // ... const int returned_value = ret_cast(int)cap(inst).get_x().ret(); printf("%d\n", returned_value); // 123 // ... cap(inst).set_x(321); printf("%d\n", inst.x); // 321 // ... finish_capture(); // ...
No, reese does not have any dependencies. Just fetch the source code and you are ready to go!
Yes. Reese does not use any platform-specific code to function.
Though not recommended, but for the sake of the argument, yes.
It uses some quirky preprocessor magic to get its job done.
Currently, reese is in an experimental stage, so it is not recommended to use it in a full-fledged development environment.
- You are not allowed to define more than 256 member functions (including the constructor) for each "class" you create.
- Reese does some dynamic allocations in the background when return values of instances are fetched. This might prove to be inefficient in situations where conserving memory is important.
- Reese is not concurrency safe.