Skip to content

04 Templated Math Library

Anders Langlands edited this page Apr 1, 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) {}

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