内容简介:One of my favorite blogs, is Raymond Chen’s “The Old New Thing”. In a recentThe 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 thi
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):
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:
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:
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 write this post because it’s good to know your options.
以上所述就是小编给大家介绍的《Avoiding compile-time recursion》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Cyberwar
Kathleen Hall Jamieson / Oxford University Press / 2018-10-3 / USD 16.96
The question of how Donald Trump won the 2016 election looms over his presidency. In particular, were the 78,000 voters who gave him an Electoral College victory affected by the Russian trolls and hac......一起来看看 《Cyberwar》 这本书的介绍吧!