0

People keep saying solve the problem using induction when it comes to template metaprograms. For example see this answer : https://stackoverflow.com/a/11811486/4882052

I know induction proofs etc, but how this theory is used to solve metaprogram?. I'd love examples with examples :)

Community
  • 1
  • 1
Angelus Mortis
  • 1,534
  • 11
  • 25
  • 2
    Do you understand "recursion"? "Induction" is just recursion looked at from a different point of view. In each you need one or more base cases in which a problem can be solved without recursion and you need a recursive case in which a problem can be solved by using the solutions of related problem(s) that are closer to base cases. – JSF Dec 18 '15 at 16:09
  • 1
    In run-time recursive programming, base cases can be detected by run-time conditionals. In recursive meta-programming, even compile-time conditionals are not quite enough to handle base cases. You need separate definitions utilizing overloading or specializing to cover base cases. – JSF Dec 18 '15 at 16:11
  • 1
    @JSF: Why is that not an answer? – Nicol Bolas Dec 18 '15 at 16:13
  • 1
    @NicolBolas because the meaning of the question wasn't clear enough and because the question requested examples and I instead provided concepts. Mainly I don't know Angelus Mortis's answer to **my** question "Do you understand recursion? – JSF Dec 18 '15 at 16:15
  • @JSF start writing answer, I'm master of recursion trust me :p – Angelus Mortis Dec 18 '15 at 16:29

3 Answers3

1

As observed by in one of the comments under the OP, the TMP technique is essentially recursive, which I guess could be seen a form of `reverse induction' (an idea originally due to Fermat). The idea is that, for some N, you define the corresponding thing you want in terms of some lesser N, eventually terminating at some base case.

Consider the following TMP code for factorial:

template <int N>
struct Factorial {
    enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<0> {
    enum { value = 1 };
};

void foo() {
    std::cout << Factorial<0>::value << "," << Factorial<3>::value; 
    // outputs 1, 6
}

So the the general case (N) is given by a template with a value defined in terms of lesser values of (potentially more specialised) templates, terminating at some lower bound.

NietzscheanAI
  • 966
  • 6
  • 16
1

An inductive proof typically has the structure:

  • show that X is (usually trivially) true for some value Y
  • Show that if X is true for Y, then it remains true for some other value Y + delta
  • Therefore conclude that X is true for all Y + delta * N

(...and in a lot of cases, it's really handy if delta is 1, so we can say "X is true for all non-negative integers", or something on that order). In a fair number of cases, it's also handy to extend the proof in both directions, so we can say that X is true for all integers (for one obvious example).

Most purely recursive solutions (whether template meta programming or otherwise) tend to follow roughly the same structure. In particular, we start with processing for some trivial case, then define the more complex cases in terms of an application of the base case plus some extending step.

Ignoring template metaprogramming for the moment, this is probably most easily seen in recursive algorithms for preorder, inorder and postorder traversal of trees. For these we define a base case for processing the data in a single node of the tree. This is usually sort of irrelevant to the tree traversal itself, so we often just treat it as a function named process or something similar. With this given, we can define tree traversals something like:

void in_order(Tree *t) { 
    if (nullptr == t)
        return;
    in_order(t->left);
    process(t);
    in_order(t->right);
}

// preorder and postorder are same except for the order of `process` vs. recursion.

The reason many people think of this as being unique (or at least unusually applicable to) template meta-programming is that it's an area where C++ really only allows purely recursive solutions--unlike normal C++, you have no loops or variables. There have been other languages like that for quite some time, but most of them haven't really reached the mainstream. There are quite a few more languages that tend to follow that style even though they don't truly require it--but while some of them have gotten closer to the mainstream, most of them are still sort of on the fringes.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
1

"Induction" is just recursion looked at from a different point of view. In each you need one or more base cases in which a problem can be solved without recursion and you need a recursive case in which a problem can be solved by using the solutions of related problem(s) that are closer to base cases.

In run-time recursive programming, base cases can be detected by run-time conditionals. In recursive meta-programming, even compile-time conditionals are not quite enough to handle base cases. You need separate definitions utilizing overloading or specializing to cover base cases.

The first time I used it myself is a rather messy situation, which I can't quote in full, but the general idea might be instructive. The compiler did various optimizations before unwinding short loops and various other optimizations after unwinding short loops. But I really needed one of those "before" optimizations done after. So I needed to force the compiler to unwind some short loops earlier in compilation, roughly:

template<unsigned N>
struct unwind {
   void operator()(X*p) { unwind<N-1>()(p); work(p[N]); } };
template<>
struct unwind<0> {
   void operator()(X*p) { work(p[0]); } };

When you use that compile-time recursion instead of a run-time loop, the compiler will unwind the whole loop before doing any of the optimization, so optimizations of a type done before loop unwinding that in my work code aren't visible until after loop unwinding will be done.

JSF
  • 5,281
  • 1
  • 13
  • 20