C++11 features in Magnum: Simplifying daily workflow

While the pre­vi­ous part was fo­cused on C++11 fea­tures im­prov­ing per­for­mance, here I will de­scribe fea­tures which are used just to sim­pli­fy your life by writ­ing more con­cise code with less mis­takes.

Vari­adic tem­plates

Vari­adic tem­plates are used ex­ten­sive­ly through­out the en­gine, but in many cas­es you won’t even no­tice. With them the en­gine is able to catch many mis­takes, which oth­er­wise would be im­pos­si­ble to de­tect, at com­pile time. Con­sid­er cre­at­ing 7-el­e­ment vec­tor the C++03 way:

Float data[] = { 3.5f, 0.3f, 4.0f, -7.3f, 5.0f, 1.0f -5.0f }; // oops
Math::Vector<7, Float> vec = Math::Vector<7, Float>::from(data);

This op­er­a­tion is not a sim­ple one-line ex­pres­sion, but it must be writ­ten in two state­ments. More­over the func­tion can­not gen­er­al­ly check that we fed it with prop­er amount of da­ta, as it gets just a Float* point­er. In Mag­num the same can be writ­ten us­ing C++11 vari­adic con­struc­tor like this:

Math::Vector<7, Float> vec(3.5f, 0.3f, 4.0f, -7.3f, 5.0f, 1.0f -5.0f); // error!

Note that un­like with e.g. ini­tial­iz­er lists, men­tioned in pre­vi­ous ar­ti­cle, the er­ror will be de­tect­ed at com­pile time — we for­got to sep­a­rate last two val­ues with a com­ma, thus passed on­ly six el­e­ments to the con­struc­tor, caus­ing un­de­fined be­hav­ior.

In oth­er sit­u­a­tions, vari­adic tem­plates might com­pute some things au­to­mat­i­cal­ly and save repet­i­tive typ­ing, which oth­er­wise leads to ac­ci­den­tal mis­takes. Imag­ine adding buf­fer with in­ter­leaved ver­tex at­tributes, spec­i­fy­ing one at­tribute at a time:

Int offset = 4238;
Int stride = 36;
Mesh mesh;
mesh.addVertexBuffer(vertexBuffer, offset, Shader::Position(), stride-12)
    .addVertexBuffer(vertexBuffer, offset+12, Shader::Normal(), stride-12)
    .addVertexBuffer(vertexBuffer, offset+24, Shader::TextureCoordinates(), stride-12)
    .addVertexBuffer(vertexBuffer, offset+32, Shader::Weight(), stride-12);

Each in­ter­leaved at­tribute is spec­i­fied by start­ing off­set and size of the gap af­ter it (con­tain­ing da­ta of oth­er at­tributes). Note that, due to copy-paste er­ror, all the at­tributes have spec­i­fied the same gap, even though the tex­ture co­or­di­nates oc­cu­py on­ly 8 bytes, not 12 and the weight is on­ly four bytes. If the whole ver­tex for­mat is spec­i­fied at once us­ing vari­adic func­tion, the sizes, off­sets and strides are com­put­ed au­to­mat­i­cal­ly be­hind the scenes, leav­ing no room for mis­takes:

mesh.addVertexBuffer(vertexBuffer, 4238, Shader::Position(), Shader::Normal(),
    Shader::TextureCoordinates(), Shader::Weight(), 3);

Last­ly, with vari­adic class­es you can merge many sim­i­lar tasks in­to one. Imag­ine hav­ing type-safe re­source man­ag­er for mesh­es, tex­tures and sound buf­fers:

ResouceManager<Mesh> meshManager;
ResouceManager<Texture2D> textureManager;
ResouceManager<Audio::Buffer> soundManager;

Mesh* mesh;
meshManager.set("steam-locomotive", mesh);
Texture* texture;
textureManager.set("steam-locomotive-diffuse", texture);
Audio::Buffer* buffer;
soundManager.set("steam-locomotive-honk", buffer);

// ...

meshManager.free();
textureManager.free();
soundManager.free();

// ...

soundManager.clear();

Vari­adic re­source man­ag­er im­ple­men­ta­tion al­lows you to sig­nif­i­cant­ly short­en the above code:

ResourceManager<Mesh, Texture2D, Audio::Buffer> manager;

manager.set("steam-locomotive", mesh)
       .set("steam-locomotive-diffuse", texture)
       .set("steam-locomotive-honk", buffer);

// ...

manager.free();

// ...

manager.clear<Audio::Buffer>();

Method chain­ing

This is not ex­act­ly a C++11 fea­ture (how­ev­er C++11’s rval­ue ref­er­ences for this can be em­ployed to im­prove per­for­mance in some cor­ner cas­es), but I will men­tion it here, as it al­lows for some neat tricks. Ex­cept for help­ing you type less (as shown above), this fea­ture al­lows you to do in­stan­ti­a­tion and con­fig­u­ra­tion in sin­gle ex­pres­sion, go­ing nice­ly along “ev­ery­t­ing is an ex­pres­sion” ap­proach:

// Configure debug shape rendering
DebugTools::set("collision-shapes", DebugTools::ShapeRendererConfiguration()
    .setColor(Color3::fromHSV(25.0f, 0.7f, 0.9f))
    .setPointSize(0.35f));

// Add more ducks to the scene
(new Duck(&scene))->translate({0.3f, 0.0f, -0.9f})->rotateY(15.0_degf);
(new Duck(&scene))->translate({0.4f, 0.0f, -1.5f})->rotateY(-5.0_degf);
(new Duck(&scene))->translate({0.5f, 0.0f, -1.1f})->rotateY(35.0_degf);

Note that this fea­ture al­so has its down­sides, so use it on­ly when it im­proves read­abil­i­ty and not the oth­er way. For ex­am­ple, more than one method chain in sin­gle ex­pres­sion can cause the code to be un­read­able and prone to er­rors.

Mis­cel­la­neous

Strong­ly typed units

API in­con­sis­tence, where some func­tions ac­cept ra­di­ans (STL and OpenGL) and some de­grees (Ope­nAL) leads to prob­lems with mis­tak­en units. C++11’s us­er-de­fined lit­er­als, ex­plic­it con­ver­sion op­er­a­tors and constexpr al­low to solve this in in­tu­itive way with­out sac­ri­fic­ing per­for­mance. The strong types be­have just like any oth­er nu­mer­ic type and on­ly con­ver­sion from and to the un­der­ly­ing type needs to be done ex­plic­it­ly. For lit­er­als, in­stead of writ­ing f suf­fix you can just write _degf or _radf and it will be con­vert­ed to the ex­pect­ed units at com­pile time. All Mag­num func­tions deal­ing with an­gles are tak­ing on­ly the strong­ly typed val­ues, pass­ing plain num­bers to them re­sults in com­pile-time er­ror:

Double sin = Math::sin(45.0_deg);
//Float cos = Math::cos(1.57f); // error
object->rotateX(15.0_degf);

Oth­er com­mon units are dis­tance and time units. Mag­num doesn’t im­pose any re­stric­tion on dis­tance units, sim­i­lar­ly to Blender, so whether 1.0f is one me­ter or one fur­long de­pends on you (but you need to be con­sis­tent, of course). Time unit lit­er­als will be part of C++14.

Us­age of SFI­NAE and type traits

With C++11’s type traits and std::en­able_if it’s pos­si­ble to de­sign clean gener­ic API free of any work­arounds for am­bigu­ous method and con­struc­tor calls. For ex­am­ple, bit­wise op­er­a­tions are en­abled on­ly for vec­tors with in­te­gral un­der­ly­ing type. Vec­tors and ma­tri­ces have gener­ic sup­port for (ex­plic­it) con­ver­sion from and to ex­ter­nal types, thanks to std::enable_if the ac­tu­al con­ver­sion can be then im­ple­ment­ed in sep­a­rate li­brary with­out touch­ing the orig­i­nal im­ple­men­ta­tion.

#include <BulletIntegration/Integration.h>

// Seamless usage of Magnum and Bullet types
btVector3 a(1.0f, 3.0f, 4.0f);
Magnum::Vector3 b(a);
b *= 5.0f;
a = btVector3(b);