Saturday, August 7, 2010

Static assert with a "real" message.

static asserts are important for many reasons and are heavily used in most large codebases esp when said codebase has many target environments. They inform the programmer when an assumption fails at compile time allowing for the runtime code to avoid expensive verification.
#define COMPILE_ASSERT(x) extern int __dummy[(int)x]

Which is typically used as
COMPILE_ASSERT(sizeof(int) == sizeof(unsigned));
COMPILE_ASSERT(sizeof(int) == sizeof(char));

the second example if obviously wrong and provides the output.
error C2466: cannot allocate an array of constant size 0

Which is not very useful other than as a signal that something bad occurred.

Now consider the form
#pragma warning (disable: 4483)

typedef char __identifier("compile assert:");

#define COMPILE_STRING_ASSERT(condition, name) \
typedef __identifier("compile assert:") __identifier(name)[1]; \
typedef __identifier("compile assert:") __identifier(name)[(condition) ? 1 : 0];

define COMPILE_ASSERT(condition) COMPILE_STRING_ASSERT(condition, #condition)

The above uses the identifier keyword which is technically an error however the pragma warning disables the error (yes it doesn't make sense). This keyword allows the user to define ANY identifier, valid C++ or not.
COMPILE_ASSERT(sizeof(int) == sizeof(char));

Now provides the output
'compile assert: sizeof(int) == sizeof(char)[1]'


which is a much more palatable message esp when considering automatic build systems.

Note2
  • __identifier is specific to Visual Studio and may not be supported by other compilers.
  • VC++ 2010 introduces static_assert for all targets, tho it is less powerful than using __identifier.
  • SNC may support both, i will try to confirm.

Becoming a console programmer : Math Libraries Part 2

Implementation

Generally speaking implementation of a math library is not determined by the programmer DOING the implementation but by the clients of the system and the platforms that require support.

Given the myriad targets currently available AND the similarly complex client base i always recommend a relatively complex abstraction built on simple rules:
  • separate the client facing api into semantic types
  • define those types using platform specific typedefs or defines
  • implement using a cross platform api with platform specific implementations.
for example we might want the client to use matrix, point, vector and proxy type for float constants.



the semantic types define their functionality in terms of the low level api which in turn defines a set of common operations that all the semantic types can use at will. This means that to provide support for a new platform for ALL semantic types (which there are usually far more than 4) the programmer simply has to satisfy the lowest level api.

That low level api is typically relatively small providing wrappers for some of the basic operations such as add, mul, madd, permute, shuffle/vsel etc whilst also implementing some of the more complex but common operations such as normalize, permute_operation etc.

To flesh things out for the client side the programmer should define operators and functions that provide mathematically sensible and desirable operations such as

  • vector = point - point,
  • point = point + vector * scale.
the proxy type mentioned above would be a simd representation of a single float allowing the math itself to remain in registers.

The key to maintaining long mathematically complex code in registers is in ensuring the semantic types are well defined within themselves and as a whole.

there is a lot more to be said on the subject of math libraries however without infringing on company policies both new and old i cannot expand further. The above should however provide a good start and from there a good programmer will run with it and discover for themselves the rest.

enjoy.