Template Metaprogramming with Modern C++: templates in depth
blog.biicode.com
blog.biicode.com
http://www.codeproject.com/Articles/11015/The-Impossibly-Fas...
By passing the address of the function to the template, the compiler knows which function is going to be called - the idea being that a smart compiler can now inline your delegates for you.
class Wrap { void call() {...} };
template <typename F> { ... F::call(); }
And with some imagination one can write a macro to do this wrapping given an existing function, used like this:
void my_func();
using MyFuncWrapped = WRAP_FUNC(my_func);
// Now MyFuncWrapped::call() calls my_func.
My implementation of the wrapper: https://github.com/ambrop72/aprinter/blob/master/aprinter/me... . But note it doesn't use std::forward since this code assumes no references being used.
C++11 lambdas have this quality of "a type for every function" as well.
I'm sorry but that's not really true. If you compile with debugging information included (with or without optimization), GCC will give you tons and tons of template name stuff in your compiled binary. I have one program that is over 100 MB and well over half of it seems to be template-related debugging stuff. It's unlikely that you could generate so many and such long symbols by hand if you weren't using templates. If you build without debugging symbols, or strip them, or separate them out, you may have different results, but I don't think that's the most common scenario for C++ users.
You can also wind up with actual instruction bloat too: a seemingly "high performance" function written with recursive templates may wind up with dozens of kilobytes of machine code thrashing your i-cache while a perfectly good "old style" function would do the same job with a hundred bytes. Disk may be cheap, but instruction cache is not.
If you use C++11/C++14 constexpr (and don't screw it up ;-), you really can be assured that the computations will complete at compile time at which point you shouldn't have very much left for the compiler to deal with at run time. I guess in theory you could have i-cache issues and such, but in practice I've yet to see that happen.
template <std::size_t N>
struct raw {
template <typename U>
constexpr raw(U x, std::enable_if<.....>);
template <typename U>
constexpr operator U();
};
template <template ContainerType, typename T, std::enable_if<std::is_trivially_convertible<T, ....> > >
struct somewhat_erased_container : private ContainerType<raw<sizeof(T)>> {
};
It's particularly easy to do with containers of pointers (of course).Just getting started on it, it seems that the theme is that you can't have the same binary code work on different types without having a run-time indirection in there somewhere. But, you can use templates to keep that indirection convenient and type safe. And, you can structure your templates to only specialize a minimal amount of code necessary to interface your type into a non-specialized algorithm.
"you can't have the same binary code work on different types without having a run-time indirection in there somewhere"
That's certainly the case, but if you're passing by reference you already have an indirection, so that may or may not mean additional run-time overhead.
True, for embedded systems this can still be a painful waste... But I'd argue if that is the case, you probably should be very explicit about using/not using STL libraries.
It's fair that if you want to optimize startup times, that first page in hurts, although generally with C++ is's more about the symbol linking overhead than anything else (and there are a lot of strategies for minimizing that, but it sure isn't want happens by default).
Other than cache, there are plenty of tricks that can cure a lot of other ills, but cache is precious.
I have to say though, I've not seen too many cases where paging was caused by bloated object code size (lots of cases where it was caused by brutal bloat due to just outright bad code).
There was a time a couple of decades ago where it was a common issue with C++, but since then RAM has gotten a lot cheaper and C++ compilers/linkers have become much smarter (not to mention all compilers/linkers getting much smarter about code size). Sure, you can find pain points, but with a modicum of effort it's really hard to imagine a project really flapping outside the working set size purely due to the compiler/linker refusing to be drink from the water it has been lead to...
Upvoted anyway for precision.
In all serious though, swapping due to code bloat (as opposed to actual runtime bloat)... I haven't seen that in ages. Caches though... like a samurai with an absurdly sharp blade... they kill you almost every time, and usually you don't even know you're dead.
Manu Sánchez will be conducting a talk about template metaprogramming in the C/C++ Madrid Meetup (Spain):
http://www.meetup.com/Madrid-C-Cpp/
You should now be able to access a style-less version of the post in the same URL.
- they can make interfaces more simple by using template methods/functions (have a single method which is specialized by the compiler, instead of polluting the API with 20 slightly different methods which only differ by the argument type)
- they can enable a coding style which 'looks and feels' almost dynamic without loosing the strict type checks and optimization opportunities that a static type system provides
- they can help to keep related data close together in memory and minimize dynamic memory allocation (e.g. an array of structs instead of an array of pointers)
- the compiler has more optimization opportunities because it has more type information (vs. a more dynamic system where types are only known at runtime)
The tradeoff is of course that the compiler generates more (specialized) code, it breaks the simple rule that the amount of generated code grows linearly with the number of lines of code. On the other hand the compiler has a lot more type info to optimize the generated code. Still the programmer has to know what's happening under the hood so he can weigh the advantages against the disadvantages.
[edit: formatting]
There is actually a tremendous amount of value of precomputing a lot of work that is currently computed at runtime (the amount of wasted CPU in your typical C++ program, particularly at start time, is kind of crazy). It's just that the real hot spots in real world programs programs tend to revolve around runtime dynamic logic.
Exactly. Templates let you do compile-time duck typing. That's extremely useful in so many scenarios.
Template "metaprogramming" is something that originated by accident, as a side effect of the original template mechanism, which was intended as a way to allow writing small generic functions. Then it got a fan club, and influence on the C++ standards committee. Now the C++ language design has gone off into template la-la land, with lots of feature support for a bad idea.
It's kind of cool that rewrite rules are Turing complete, and that C++ lets you do things like compute Fibonacci numbers at compile time using recursive rewrite rules. This is a terrible way to program. Someday, someone will have to debug the thing, and it won't be easy.
There's a long history of this sort of clever stupidity. LISP doesn't have a FOR statement. So one was created as a compile-time macro. Here's the MIT Loop Macro, Common LISP version:
ftp://ftp.cs.cmu.edu/user/ai/lang/lisp/code/iter/loop/mit/mit_loop.cl
All 1496 lines of it. It's very clever. It took over a decade to debug.
Don't go there.
template<std::size_t count , typename STRING>
struct build_string_impl;
I think the above is a template that resolves to a forward declaration of a struct type called build_string_impl. It says, give me a size and a type STRING, and I give you a struct type. template<std::size_t count , char... Cs>
struct build_string_impl<count,string<Cs...>>
{
using result = typename build_string_impl<count-1,string<c+count,Cs...>>::result;
};
I think the above is a partial specialization of the above forward declaration. In this specialization, the typename STRING is not a string, but instead a variadic char-thingy. So the 'typename STRING' is misleading. This partial specialization creates a struct type that contains a member type 'result' that aliases the 'result' type member of a recursive template instantiation. template<char... Cs>
struct build_string_impl<0,string<Cs...>>
{
using result = string<c,Cs...>;
};
This is a partial specialization that represents the base case. It declares a template that creates a struct type build_string_impl that contains a member type 'result' that aliases the 'result' of a string type.Where I get lost is here:
build_string_impl<count-1,string<c+count,Cs...>>
This attempts to instantiate the build_string_impl template with a size_t and a string. But there is no implementation of the template with these types - only for size_t and variadic char. So how does this get instantiated?The point of the example is: We represent a string as a variadic pack of chars, and build_string is a metafunction that builds that kind of string recursively given a starting character and a size. Inside that metafunction we define an aux metafunction build_string_impl that does the job, we only have to pass the required initial parameters (The count of chars and the initial empty string). The first partial specialization acts as the recursive case of that function, and the second is the base case (note count is 0 in that case).
Thanks to updates to the language, some of the hackery in there is no longer nearly so devilish, and there the language standard provides idioms that are simpler but "good enough if not better" for most people (tuples, smart pointers, move semantics, etc.), so a lot of the specific designs in the book _are_ starting to become passé. Still, it is a fantastic book for understanding the expressive power of C++'s multiparadigm design.
First, GoF and design patterns in general isn't really meant to be "copy and paste" code. They're very clear that the code is provided merely for illustrative purposes.
Most of the design patterns in the GoF book are actually cribbed/realized in standard libraries of various languages where they work quite well. The problem is, those languages look almost nothing like C++. You could describe it as "biased with OOP", but it's really more that C++'s peculiarities (value semantics, comparatively complex and tightly coupled inheritance semantics, a purely functional meta-object protocol with very limited reflection semantics, and yes, it's multi-paradigm approach) make it serve as a bad fit.
Ironically though, much of MCPP is proving C++-style manifestations of GoF design patterns...