What's This Do?

Programming etc.

Tag Archives: openal

Attribute Access using C++ Templates – Part I

Introduction

When wrapping a C library in C++ types, it’s important to leverage the tools that C++ provides. Otherwise you end up with the same library, only the functions are organised into classes and you (hopefully) have some RAII. This makes it a little more “C++y”, but it doesn’t really make it any better.

One of C++‘s strongest features is templates; many scenarios which would require run-time checking and excessive code duplication can be avoided by providing a simple, generic interface and compile-time instantiation. A recent project, wrapping OpenAL in C++, provided an opportunity to demonstrate the power of templates for attribute access.
OpenAL Logo

Attributes

The things that all entities in a Universe of Discourse have in common are attributes. Everything within a problem domain has a non-empty set of attributes, else it’s not a thing. Some libraries, like OpenAL, expose objects which are attribute-heavy. Types such as source or listener have attributes like velocity, direction, gain, position, orientation etc., all of which are used to represent sounds and listeners in a 3D world. Each attribute of each entity is taken into account to determine, ultimately, what comes out of the user’s audio device.

Attribute Types

Attributes become more complex when taking type into account. Each attribute in OpenAL is represented either by a single int or float (or double), or by an array of them. Accessing attributes (getting and setting) is done through APIs which follow a common naming scheme.

For example, the function alGetSourcefv(...) tells the programmer that the first argument is the name (ID) of the source, the second argument is the attribute to get, and the third argument is an array of floating-point values which will be populated with the appropriate values after the call returns.

Multiple Types

Some attributes of OpenAL types can be accessed by both integer and floating-point types. Thus, calls to alGetSourceiv and alGetSourcefv are equally valid for some attributes.

Main Problem: User is Responsible

The above method achieves a level of genericity, yet it causes a new problem. The onus is now on the client code to know what types are valid for which attributes. Additionally, the programmer must also be aware of the attribute type they are using, and which API is appropriate to call.

Naive Attempt

When designing the class wrapper around OpenAL types, it may be tempting to simply expose each valid permutation of the attribute access functions as a separate, overloaded member function of the class:

....
void setPosition(float x, float y, float z);
void setPosition(const float *xyz);
void getPosition(float &x, float &y, float &z) const;
void getPosition(float *xyz) const;

void setPosition(int x, int y, int z);
void setPosition(const int *xyz);
void getPosition(int &x, int &y, int &z) const;
void getPosition(int *xyz) const;

void setGain(float g);
float getGain() const;
....

By doing this, you have solved the first problem; users can simply call getPosition or setPosition with the appropriate parameters, and the corresponding API will be called. Modern compilers will inline these calls too, which means the call to your C++ code costs nothing.

Problem 1: Code Duplication

However, there is now a new problem: you will need to duplicate your code. For every attribute! You have re-introduced the initial problem that the authors of OpenAL were trying to avoid.

So maybe you could drop the function name suffix and have the user pass in the name of the attribute to access:

....
void set(ALenum attr, float x, float y, float z);
void set(ALenum attr, const float *xyz);
void get(ALenum attr, float &x, float &y, float &z) const;
void get(ALenum attr, float *xyz) const;

void set(ALenum attr, int x, int y, int z);
void set(ALenum attr, const int *xyz);
void get(ALenum attr, int &x, int &y, int &z) const;
void get(ALenum attr, int *xyz) const;

void set(ALenum attr, float g);
float get(ALenum attr) const;
....

This really is no better than the C interface. It leaves the door open for the client code to call float v = source.get(AL_VEOCITY);, which will result in an error; velocity is a 3-float tuple attribute.

Problem 2: Custom Types

And what happens when you want users to be able to pass an std::vector? You could expect them to pass it in as an array (&a[0]), or you could write yet another overload, for each attribute, that takes the container as a parameter. Sheesh. Furthermore, what about when the client wants to pass in their own custom position type? Perhaps they could write a wrapper around your wrapper? No thanks!Yo dawg

Part I

Enter templates. The technique that follows may be a little more challenging, but I believe it leads to a cleaner, simpler, more intuitive interface for attribute access.

Handling Custom Types

Suppose you want to accept any old class as an attribute provider. The following method allows you to treat any class as a sequential array:

/* This is the default way, using the subscript operator.
   Should work for sequential STL containers, arrays etc. */
template <typename Container, int Index, typename Value>
struct access {
    static const Value &get(const Container &c) { return c[Index]; }
    static Value &get(Container &c) { return c[Index]; }
};

//Define the type of an attribute accessor
template <typename T>
struct attribute_type {
    typedef T type;
};

//Partial specialisation for arrays
template <typename T>
struct attribute_type<T*> {
    typedef T type;
};

//Get element in an attribute accessor
template <int I, typename T>
const typename attribute_type<T>::type &get(const T &c) {
    return access<T, I, typename attribute_type<T>::type>::get(c);
}

//Get element in an attribute accessor
template <int I, typename T>
typename attribute_type<T>::type &get(T &c) {
    return access<T, I, typename attribute_type<T>::type>::get(c);
}

The above code allows you to call get<n>(v); on any type of v and have a reference to the n-th value returned. It works out of the box for arrays! To have it work with other types which provide a subscript operator overload, simply specialise the attribute_type template:

template <typename T>
struct attribute_type <std::vector<T> > {
    typedef typename std::vector<T>::value_type type;
};

Specialising the Access Template

Suppose the client wants to use his own 3D vector type to get/set positions:

/* A crude 3D vector */
class Vector3f {
public:
    float x, y, z;
};

You can still use this type as an attribute provider by specialising a couple of templates:

/* Let the library know what intrinsic type is provided */
template <>
struct attribute_type<Vector3f> {
    typedef float type;
};

/* Overload the access struct so that indexes 0, 1, and 2 correspond to
   members x, y, and z of the Vector3f type respectively */
struct access <Vector3f, 0, float> {
    static const float &get(const Vector3f &v) { return v.x; }
    static float &get(Vector3f &v) { return v.x; }
};
struct access <Vector3f, 1, float> {
    static const float &get(const Vector3f &v) { return v.y; }
    static float &get(Vector3f &v) { return v.y; }
};
struct access <Vector3f, 2, float> {
    static const float &get(const Vector3f &v) { return v.z; }
    static float &get(Vector3f &v) { return v.z; }
};

Now, get<0>(v) can also be called on any Vector3f, and a reference to v.x will be returned.

You can use virtually any type to get and set values through generic template functions. These semantics will be used when defining generic getters and setters which will work for any attribute and any attribute provider.

Here’s an example usage of the technique:

template <typename T, typename V>
struct printer {
    //Assuming T is a 3D vector type, print x, y, z components.
    static void print(const T &t) {
        using namespace std; //If you're using C++11, this allows you to use tuples.
        cout << get<0>(t) << endl <<
                get<1>(t) << endl <<
                get<2>(t) << endl;
    }
};

//Specialise for single values
template <typename T>
struct printer<T, T> {
    //Print the value of t
    static void print(const T &t) {
        std::cout << t << std::endl;
    }
};

//This function accesses single intrinsic types or 
// custom 3D vector providers. You can pass it
// floats, ints, std::vectors, arrays or custom
// types (provided the correct templates are
// specialised).
template <typename T>
void print(const T& t) {
    printer<T, typename attribute_type<T>::type>::print(t);
}

Coming Up Next

intuitive
The rest of the solution is long-winded, and challenging to describe in a compact way. It probably warrants being split out amongst a number of posts.

If there is demand for it, the remaining steps will be published in time.

Suffice to say, the end result makes for elegant, succinct, and intuitive code:

//This is a sample of what it's like to interface with the wrapper
source src;
src.generate();

//Set position using seperate values
src.set<position>(1, 1, 1);

//Set the maximum gain by a single float
src.set<max_gain>(0.3f);

//Set the direction using a vector of floats
std::vector<float> dir(3, 0);
src.set<direction>(dir);

//Get the vector as an array
float dir2[3];
src.get<direction>(dir2);

assert(memcmp(&dir[0], dir2, sizeof(float) * 3) == 0);
Follow

Get every new post delivered to your Inbox.

Join 41 other followers