-
Notifications
You must be signed in to change notification settings - Fork 6
04 Templated Math Library
Let's make our simple vector class templated so we can store floats, doubles, ints, whatever we like. Starting from the last lesson that would give us this library type:
#include <math.h>
// Define the internal versioned namespace
#define MYMATH_INTERNAL_NAMESPACE Mymath_1_1
// Declare everything in the versioned namespace
namespace MYMATH_INTERNAL_NAMESPACE {
// A simple 3d vector type
template <typename T> struct Vec3 {
T x;
T y;
T z;
// Default 0-initialize
Vec3() : x(0), y(0), z(0) {}
// Initialize all members
Vec3(T x, T y, T z) : x(x), y(y), z(z) {}
// Initialize all members with a single value
Vec3(T v) : x(v), y(v), z(v) {}
// Copy constructor
Vec3(const Vec3& v) : x(v.x), y(v.y), z(v.z) {}
T length() const { return sqrt(x * x + y * y + z * z); }
};
} // namespace MYMATH_INTERNAL_NAMESPACE
// Pull all symbols into public namespace
namespace Mymath {
using namespace MYMATH_INTERNAL_NAMESPACE;
}
Modifying the binding file is essentially what you'd expect. Note though that we need to write out the full Vec3<T>
everywhere it appears in method signiatures and in the BoundType
alias.
#include <math.hpp>
#include <cppmm_bind.hpp>
namespace cppmm_bind {
namespace MYMATH_INTERNAL_NAMESPACE {
namespace Mymath = ::MYMATH_INTERNAL_NAMESPACE;
template <typename T> struct Vec3 {
using BoundType = Mymath::Vec3<T>;
Vec3() CPPMM_RENAME(default);
Vec3(T x, T y, T z) CPPMM_RENAME(new);
Vec3(T v) CPPMM_RENAME(from_scalar);
Vec3(const Mymath::Vec3<T>& v) CPPMM_RENAME(copy);
T length() const;
} CPPMM_VALUETYPE;
} // namespace MYMATH_INTERNAL_NAMESPACE
} // namespace cppmm_bind
if we run astgen on this:
./astgen/astgen 04_templates/bind/c-math.cpp -v 1 -o 04_templates/ast -- -I./04_templates/include
we get... nothing? If you think about it, this makes sense. C++ only instantiates templates we actually use, so as far as astgen is concerned, that template definition doesn't exist. Moreover, C of course has no concept of generics so we need to explicitly specify what types we want to create from that template.
The way we do this is with explicit template instantiation. This is straightforward but a little fiddly, so pay close attention to the changes we're making here. First, we need to explicitly instantiate the binding class for the types we want. Let's say we want Vec3<int>
and Vec3<float>
. So we add the following lines just after the binding struct definition:
template <typename T>
struct Vec3 {
// ...
} CPPMM_VALUETYPE;
template class Vec3<float>; // instantiate binding for float
template class Vec3<int>; // instantiate binding for int
Next, we need to instantiate the library type in the same way, otherwise the binding will know about the float and int versions of cppmm_bind::Mymath::Vec3
but clang will never generate the corresponding AST for Mymath::Vec3
. To do that we do the same thing as above, but targeting the library type and in the global namespace. So add the following at the very bottom of the binding file:
namespace cppmm_bind {
// ...
} // namespace cppmm_bind
template class Mymath::Vec3<float>; // instantiate library Vec3 for float
template class Mymath::Vec3<int>; // instantiate library Vec3 for int
Finally, Vec3<float>
and Vec3<int>
aren't valid C identifiers, so we need to give our types C-friendly names. The way we do this is by defining an alias for the library types next to the binding names, like this:
template <typename T>
struct Vec3 {
// ...
} CPPMM_VALUETYPE;
template class Vec3<float>; // instantiate binding for float
template class Vec3<int>; // instantiate binding for int
using V3f = Mymath::Vec3<float>; // name the float instantiation
using V3i = Mymath::Vec3<int>; // name the int instantiation
So this gives us the following complete binding file:
#include <math.hpp>
#include <cppmm_bind.hpp>
namespace cppmm_bind {
namespace MYMATH_INTERNAL_NAMESPACE {
namespace Mymath = ::MYMATH_INTERNAL_NAMESPACE;
template <typename T> struct Vec3 {
using BoundType = Mymath::Vec3<T>;
Vec3() CPPMM_RENAME(default);
Vec3(T x, T y, T z) CPPMM_RENAME(new);
Vec3(T v) CPPMM_RENAME(from_scalar);
Vec3(const Mymath::Vec3<T>& v) CPPMM_RENAME(copy);
T length() const;
} CPPMM_VALUETYPE;
template class Vec3<float>; // instantiate binding for float
template class Vec3<int>; // instantiate binding for int
using V3f = Mymath::Vec3<float>;
using V3i = Mymath::Vec3<int>;
} // namespace MYMATH_INTERNAL_NAMESPACE
} // namespace cppmm_bind
template class Mymath::Vec3<float>; // instantiate library Vec3 for float
template class Mymath::Vec3<int>; // instantiate library Vec3 for int
And if we run astgen and asttoc on it:
./astgen/astgen 04_templates/bind/c-math.cpp -v 1 -o 04_templates/ast -- -I./04_templates/include
./asttoc/asttoc 04_templates/ast -o 04_templates -p math
Then we get the following C API where we can see all methods have been generate with the correct types for each struct instantiation
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
typedef struct Mymath_1_1__Vec3_float__t_s {
float x;
float y;
float z;
} __attribute__((aligned(4))) Mymath_1_1__Vec3_float__t;
typedef Mymath_1_1__Vec3_float__t Mymath_V3f_t;
typedef struct Mymath_1_1__Vec3_int__t_s {
int x;
int y;
int z;
} __attribute__((aligned(4))) Mymath_1_1__Vec3_int__t;
typedef Mymath_1_1__Vec3_int__t Mymath_V3i_t;
void Mymath_1_1__Vec3_float__default(Mymath_V3f_t* this_);
#define Mymath_V3f_default Mymath_1_1__Vec3_float__default
void Mymath_1_1__Vec3_float__new(Mymath_V3f_t* this_, float x, float y,
float z);
#define Mymath_V3f_new Mymath_1_1__Vec3_float__new
void Mymath_1_1__Vec3_float__from_scalar(Mymath_V3f_t* this_, float v);
#define Mymath_V3f_from_scalar Mymath_1_1__Vec3_float__from_scalar
void Mymath_1_1__Vec3_float__copy(Mymath_V3f_t* this_, Mymath_V3f_t const* v);
#define Mymath_V3f_copy Mymath_1_1__Vec3_float__copy
float Mymath_1_1__Vec3_float__length(Mymath_V3f_t const* this_);
#define Mymath_V3f_length Mymath_1_1__Vec3_float__length
void Mymath_1_1__Vec3_int__default(Mymath_V3i_t* this_);
#define Mymath_V3i_default Mymath_1_1__Vec3_int__default
void Mymath_1_1__Vec3_int__new(Mymath_V3i_t* this_, int x, int y, int z);
#define Mymath_V3i_new Mymath_1_1__Vec3_int__new
void Mymath_1_1__Vec3_int__from_scalar(Mymath_V3i_t* this_, int v);
#define Mymath_V3i_from_scalar Mymath_1_1__Vec3_int__from_scalar
void Mymath_1_1__Vec3_int__copy(Mymath_V3i_t* this_, Mymath_V3i_t const* v);
#define Mymath_V3i_copy Mymath_1_1__Vec3_int__copy
int Mymath_1_1__Vec3_int__length(Mymath_V3i_t const* this_);
#define Mymath_V3i_length Mymath_1_1__Vec3_int__length
#ifdef __cplusplus
}
#endif