# C++ Conditional Compiling Using constexpr and Templates

Written on July 9, 2020

After another year of game industry C++ experience coupled with watching the CppCon 2017: constexpr ALL the Things! talk of Jason Turner & Ben Deane, I decided it is time to have some meta programming fun using the C++17 `constexpr` features while revisiting some old code and making improvements on it.

# Problem

To give some context:

• I’m generating some data that represents a triangle in 3D space
• Consists of a collection of Vertices & Indices
• In addition to the 3D position, the Vertices can have some additional attributes such as
• texture coordinates (uv)
• normal/tangent vectors
• color etc.

Let’s ignore the details of conversions from `vec3` to `float` and the `Mesh` struct for simplicity

At the time this function was written, it was good enough to have ALL the vertices of the same data layout for the scope of the project. However, not all vertices actually needed all the attributes. This can be bad! Having redundant/unused data in the vertex buffer will lead to less optimized cache & bandwidth usage on the GPU when rendering geometry, which can result in a performance penalty depending on the application’s graphics workload.

In practice, (Positions + Texture Coordinates) would suffice while rendering a fullscreen triangle for Ambient Occlusion, Post Processing & Deferred Lighting passes. On the other hand, the Normal Vectors would be needed in addition to the (Positions + Texture Coordinates) for the lighting calculations if the triangle was being used in the 3D scene and was lit.

Template metaprogramming to the rescue!

# Solution

If we slice up the vertex data, we end up using a combination of the vertex attributes:

Now that we have different types of Vertices, we will need to use `template`s when representing the geometry data. While we’re at templating the vertices, why not do the same for indices too? After all, they can be either 16-bits or 32-bits wide.

Let’s define a generic data type representing the geometry:

Next, refactoring the original geometry generator function to

• be generic for all Vertex and Index types by taking in template arguments
• change the return type to the generic geometry data type we just defined

We can adjust the triangle generation algorithm for the generic vertex data type as follows:

• assign `positions`
• assign `uv` texture coordinates
• assign `normals` IF vertex has normals
• assign `tangents` IF vertex has tangents
• assign `color` IF vertex has color

C++17 expands the context of `constexpr` and makes it usable with `if`/`else` blocks to allow for conditional compilation. We can use `constexpr if` in conjunction with the `std::is_same<T1, T2>` shown earlier to make the compiler generate code based on the Vertex type provided by the template argument `TVertex`.

We start with defining some compile-time `bool` variables, then calculate the indices, `position` and `uv` as they’re shared by all the vertex types:

Finally, we can use the `constexpr if` for the conditional compilation.

The complete function would look like this:

Here’s an example how the caller would invoke the function with different vertex types:

This setup will work as long as the vertex buffer definitions keep the naming consistent among different types.
In other words, there cannot be a vertex type with `float position` and another with `float pos`

With this small constraint, the compiler will generate code based on the vertex type, resulting in a more flexible geometry generator and I find this pretty neat!
{"ft_lang_label":"__label__en","ft_lang_prob":0.84261876,"math_prob":0.8248408,"size":3482,"snap":"2020-34-2020-40","text_gpt3_token_len":711,"char_repetition_ratio":0.118171364,"word_repetition_ratio":0.003539823,"special_character_ratio":0.1973004,"punctuation_ratio":0.07753164,"nsfw_num_words":0,"has_unicode_error":false,"math_prob_llama3":0.9557413,"pos_list":[0],"im_url_duplicate_count":[null],"WARC_HEADER":"{\"WARC-Type\":\"response\",\"WARC-Date\":\"2020-08-10T08:36:23Z\",\"WARC-Record-ID\":\"<urn:uuid:854ae690-c5f1-4379-bf60-736a5b30ddcc>\",\"Content-Length\":\"46058\",\"Content-Type\":\"application/http; msgtype=response\",\"WARC-Warcinfo-ID\":\"<urn:uuid:3416287d-13c4-4282-aa71-7041ec7ed9d4>\",\"WARC-Concurrent-To\":\"<urn:uuid:5631b796-3dd7-4f5c-a9fb-bd4716ba92ea>\",\"WARC-IP-Address\":\"185.199.108.153\",\"WARC-Target-URI\":\"https://vilbeyli.github.io/C++-Conditional-Compiling-using-constexpr/\",\"WARC-Payload-Digest\":\"sha1:6BIBF5KUN3Q6SV3OB7N47HVJ3BEICABV\",\"WARC-Block-Digest\":\"sha1:Q7V2TO2ZALVKZLZDKSGMLO2LV3TN7N63\",\"WARC-Identified-Payload-Type\":\"text/html\",\"warc_filename\":\"/cc_download/warc_2020/CC-MAIN-2020-34/CC-MAIN-2020-34_segments_1596439738653.47_warc_CC-MAIN-20200810072511-20200810102511-00056.warc.gz\"}"}