-
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) {}
float 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;
};
} // 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;
};
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/c
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_V3f_s {
char data[12];
} __attribute__((aligned(4))) Mymath_1_1_V3f;
typedef struct Mymath_1_1_V3i_s {
char data[12];
} __attribute__((aligned(4))) Mymath_1_1_V3i;
void Mymath_1_1_V3f_default(
Mymath_1_1_V3f * this_);
void Mymath_1_1_V3f_new(
Mymath_1_1_V3f * this_
, float x
, float y
, float z);
void Mymath_1_1_V3f_from_scalar(
Mymath_1_1_V3f * this_
, float v);
void Mymath_1_1_V3f_copy(
Mymath_1_1_V3f * this_
, Mymath_1_1_V3f const * v);
float Mymath_1_1_V3f_length(
Mymath_1_1_V3f const * this_);
void Mymath_1_1_V3i_default(
Mymath_1_1_V3i * this_);
void Mymath_1_1_V3i_new(
Mymath_1_1_V3i * this_
, int x
, int y
, int z);
void Mymath_1_1_V3i_from_scalar(
Mymath_1_1_V3i * this_
, int v);
void Mymath_1_1_V3i_copy(
Mymath_1_1_V3i * this_
, Mymath_1_1_V3i const * v);
float Mymath_1_1_V3i_length(
Mymath_1_1_V3i const * this_);
#ifdef __cplusplus
}
#endif