Skip to content

04 Templated Math Library

Anders Langlands edited this page Apr 17, 2021 · 3 revisions

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
Clone this wiki locally