One of my favorite blogs, is Raymond Chen’s “The Old New Thing”. In a recent post, he described a way to **find a type in a tuple**, with a compile time predicate that given a type and a tuple, will return the index of that type in the tuple, or fail to compile if the type does not exist (or exists more than once):

type_index<int, std::tuple<char, int, float>>::value; | |

// Our type^^^ is found at index ^^^ 1 of our tuple |

The value is calculated at compile time and all is good in the world, no?

You can follow the link to check his implementation, Raymond offers a great walk-through as always. The thing that bothers me is the use of **recursive template instantiations.** This happens when a template instantiates itself until it hits a termination case. The reason I dislike the technique is because it tends to:

- Increase the binary size. Symbols are generated for a chain of instantiations which end up bloating the executable.
- Slow down compilation. It’s a recursion and the compiler has to pay the price up front.
- Complicate the error messages when something goes wrong. The compiler will spit out the whole recursion tree when something goes wrong, and the error messages get obfuscated.

There’s a chapter called “The Cost of Recursive Instantiation” in the template book, but what I’ve found really dangerous is that when trying to scale code designed this way the burden on the compiler may get out of hand. E.g. we may start adding nested types in such templates or try to piggy back on the recursion to design extra machinery.

Of course in the problem at hand, Raymond’s implementation is simple and efficient (even checked binary outputs and resulting generated symbol – as always you can trust what he’s saying). **I’m writing this post to show there’s usually an alternative and to point out that not considering the cost of these things may haunt you in the future**.

Let’s see how a non recursive implementation would look like:

template <class T, std::size_t I, class Tuple> // 1 | |

constexpr bool match_v = std::is_same_v<T, std::tuple_element_t<I, Tuple>>; | |

template <class T, class Tuple, // 2 | |

class Idxs = std::make_index_sequence<std::tuple_size_v<Tuple>>> | |

struct type_index; | |

template <class T, template <class…> class Tuple, class… Args, // 3 | |

std::size_t… Is> | |

struct type_index<T, Tuple<Args…>, std::index_sequence<Is…>> | |

: std::integral_constant<std::size_t, | |

((Is * match_v<T, Is, Tuple<Args…>>) + … + 0)> { | |

static_assert(1 == (match_v<T, Is, Tuple<Args…>> + … + 0), | |

"T doesn't appear once in Tuple"); | |

}; |

I’m doing all the work at (3) with this guy:

Is * match_v<T, Is, Tuple<Args...>> + ... + 0

This fold is binary to handle empty parameter packs. The “match_v” predicate (1) checks if a type “T” exist at index “I” in a tuple “Tuple”. We can see what an example instantiation would do:

type_index<int, tuple<char, bool, int>> // T = int // Tuple = tuple<char, bool, int> // Args... = char, bool, int // Is = 0, 1, 2 -> index sequence of cardinality = sizeof...(Args) // If we denote as "m" the "match_v" predicate, we have (0 * m<int,0,Tuple> + (1 * m<int,1,Tuple> + (2 * m<int,2,Tuple>))) (0 * 0 + (1 * 0 + (2 * 1))) = 2 2 -> the index of "int" inside Tuple

Essentially we multiply each index with the predicate’s result, and sum the results. The predicate (1) could be written inline, but since there’s a second use for it, it’s good to abstract it out.

Our specialization has a static assertion. By using a variation of the fold expression described above, it makes sure that only one occurrence of the type “T” exists in “Tuple”. This way, when our meta-function returns zero, we can be positive it means “index zero” instead of “does not exist”; additionally we make sure that if the type exists more than once in the tuple, a static assertion is thrown much like in the original implementation.

Check a demo here.

## A peace offering

I understand that flattening template instantiations might not appeal to some people, so let me extend an olive branch here: when done right, compile time recursion can be OK. Take for example the “index_sequence” we use in our code, which is implemented recursively:

- Implementations use an amazing trick (I believe it’s attributed to Dave Abrahams but don’t quote me on that – it certainly appeared in his work a long time ago) to lower recursion depth to Log2(N). It is based on the realization that you can use the first half of an index sequence to build the second half, by adding an initial offset:

index_sequence<20> = index_sequence<10, 10 + index_sequence<10>>

- Ubiquitous library facilities are reusable. What this means, is that if someone instantiated an index sequence of twenty elements, most probably this will be shareable in your implementation.

Finally, if we end up using recursion (hope not but…), simplicity should compensate for potential hazards. Here’s how I’d solve the same problem recursively, with a single function:

template <class T, class Tuple, std::size_t I = 0> | |

consteval std::size_t TypeIndex() { | |

if constexpr (I >= std::tuple_size<Tuple>::value) { | |

return 0; | |

} else if constexpr (std::is_same_v<T, std::tuple_element_t<I, Tuple>>) { | |

return I + 1; | |

} else { | |

return TypeIndex<T, Tuple, I + 1>(); | |

} | |

} |

Check a demo here.

Not much explanation is needed apart from the fact that I’ve **opted to return the 1-based index **and use **0 when the search fails**, instead of a compilation error. We also avoid compilation error when more that one instances occur, by returning the index of the first match, thus short-circuiting template instantiations.

## Disclaimer

If you’re in a situation where such things matter, always always measure: measure compilation time, executable size, check generated symbols, check compilation in different platforms (time and again I’ve seen VS struggle with sizes & symbol generation that gcc would simply optimize out and discard). I only wrote this post because it’s good to know your options.