0

Studying few of the template programs and especially meta programs which deduce result at compile time into a constant, I learned that generally there is only one way to implement something. e.g.: as easy as factorial example or as complex as is_base_of

I am never able to think about perfect alternate implementation of such code which has completely different logic. Is this a true assumption ?

If this assumption is true that means, whenever we implement something using template tricks, we are always assured that, it's the best code and we don't have to worry about optimizing compilation time anymore.

[Note: I am not mentioning about general template usage which we do with class and functions. But the usage for deducing a compile time constant.]

Community
  • 1
  • 1
iammilind
  • 68,093
  • 33
  • 169
  • 336
  • Do you mean only one way to implement the template, or only one way to implement the code _using_ the template? – Ben Hocking Jun 19 '11 at 12:51
  • @Ben Hocking, 2nd one. Only one way to code using `template`. You can take the example of `is_base_of` I linked above. It has only way to get implemented using templates. In fact I tried to implement myself http://stackoverflow.com/questions/5770467/alternate-implementation-of-is-base-of-checking-base-derived-relationship. with completely different logic, but still it's not the perfect alternative. – iammilind Jun 19 '11 at 12:54
  • are you asking if the template instantiation recursion order is defined in the standard? – David Heffernan Jun 19 '11 at 13:00
  • @iammilind: It seems you've already answered your question, since you've already implemented a second version. Are you really asking if there's only one _best_ implementation? – Ben Hocking Jun 19 '11 at 13:07
  • @David Heffernan, not really. I am asking that, if we implement anything using metaprogramming or template programming to deduce a compile time constant; is there any 2nd way (I suppose there is no 2nd way). – iammilind Jun 19 '11 at 13:07
  • @Ben Hocking, what I have implemented is lacking some functionality. It's NOT a perfect alternative. I feel there is never a 2nd way. – iammilind Jun 19 '11 at 13:08
  • 5
    are you really asking if there exist functions that can be implemented in two different ways? That sounds like a bizarre question. – David Heffernan Jun 19 '11 at 13:11
  • 2
    The skill required for implementing a function using template meta-programming is the same skill that is needed for *pure functional programming*. Thus, this question boils down to: is there always only one way of implementing (something) in pure functional programming? – rwong Jun 19 '11 at 13:30

5 Answers5

5

The reason you tend to only see one way of doing something is because the things that people actually use template meta-programming for are usually algorithmically trivial -- they just look complicated because they get mixed up with a load of type hackery and the oddities of C++ template syntax.

But sometimes (as Steve Jessop's answer shows) there really are multiple algorithms to calculate something, and you can implement any of them with templates.

As another example, here are two ways to evaluate pow(a,b) (for small integer arguments):

Obvious:

// ----- Simple Way -----

template <int A, int B>
struct PowA {
    typedef PowA<A,B-1> next;
    enum {
       value = A * next::value,
       recursion_count = 1 + next::recursion_count
    };
};
template <int A> struct PowA<A, 1> { enum { value = A, recursion_count = 0 }; };
template <int A> struct PowA<A, 0> { enum { value = 1, recursion_count = 0 }; };

Slightly less obvious:

// ----- Less Simple Way -----

template <int A, int B, int IsOdd> struct PowHelper;

template <int A> struct PowHelper<A, 0, 0> { enum { value = 1, recursion_count = 0 }; };
template <int A> struct PowHelper<A, 1, 1> { enum { value = A, recursion_count = 0 }; };

template <int A, int B>
struct PowHelper<A, B, 1> {
    typedef PowHelper<A, B-1, 1> next;
    enum {
        value = A * next::value,
        recursion_count = 1 + next::recursion_count
    };
};
template <int A, int B>
struct PowHelper<A, B, 0> {
    typedef PowHelper<A, B/2, ((B/2)&1)> next;
    enum {
        x = next::value,
        value = x*x,
        recursion_count = 1 + next::recursion_count
    };
};

template <int A, int B>
struct PowB {
    typedef PowHelper<A,B,(B & 1)> helper;
    enum {
        value = helper::value,
        recursion_count = helper::recursion_count
    };
};

And some code to let you check it:

// ----- Test -----

#include <iostream>

int main(int argc, char* argv[]) {
#define CHECK(X,Y) \
    std::cout << ("PowA: " #X "**" #Y " = ") << \
        PowA<(X),(Y)>::value << " (recurses " << \
        PowA<(X),(Y)>::recursion_count << " times)" << std::endl; \
    std::cout << ("PowB: " #X "**" #Y " = ") << \
        PowB<(X),(Y)>::value << " (recurses " << \
        PowB<(X),(Y)>::recursion_count << " times)" << std::endl;

    CHECK(3,3)
    CHECK(2,8)
    CHECK(7,3)
    CHECK(3,18)

#undef CHECK

   return 0;
}
John Bartholomew
  • 6,428
  • 1
  • 30
  • 39
  • I see `x*x` in that second mess, so I'm going to assume without checking that it's exponentiation by squaring and retreat to a safe place. +1 since there's a clear tradeoff here between simplicity of implementation, and expected performance (in terms of both compile time and recursion depth, which is typically rather limited in TMP). – Steve Jessop Jun 19 '11 at 17:39
3

Sort of depends what you mean by "doing it differently". Here are two TMP implementations to compute a triangle number:

template<int N>
struct RecursiveTriangle {
    static const int value = RecursiveTriangle<N-1>::value + N;
};

template<>
struct RecursiveTriangle<0> {
    static const int value = 0;
};

template<int N>
struct Triangle {
    static const int value = (N*(N+1))/2;
};

These are precisely analagous to two "different" ways of computing triangle numbers imperatively - with a loop or with the same formula as Triangle. Their domain of definition differs, though - Triangle handles negative numbers, and RecursiveTriangle doesn't. Not that the result of Triangle for negative numbers makes much sense.

So, what do you mean by "different ways to do it"?

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • +1 This is actually a nice example. However mostly a person would opt for the 2nd solution straight away. – iammilind Jun 19 '11 at 17:10
  • @iammilind: yes, in this case one algorithm is clearly superior to the other. But it certainly demonstrates that it is possible, in functional programming in general and in TMP in particular, to implement different algorithms to produce the same result. In practice, if there isn't a choice of useful algorithms in TMP situations then that's probably because in practice TMP isn't usually used to do anything particularly complicated. If you were implementing Boost.Spirit then you might have different ideas about whether there's one obvious solution. – Steve Jessop Jun 19 '11 at 17:18
1

No, there's not only one implementation. Here's an example of two ways of doing factorial:

#include <iostream>
using namespace std;

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

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

template <int N>
class FactorialC {
public:
  static const long value = N * Factorial<N - 1>::value;
};

template <>
class FactorialC<10> {
public:
  static const long value = 3628800;
};

template <>
class FactorialC<0> {
public:
  static const long value = 0;
};

int main() {
  cout << Factorial<4>::value << endl;
  cout << Factorial<12>::value << endl;
  cout << FactorialC<4>::value << endl;
  cout << FactorialC<12>::value << endl;
}

Output:

24
479001600
24
479001600
Ben Hocking
  • 7,790
  • 5
  • 37
  • 52
  • @David Heffernan: Good catch (I've since edited it). I had written in the reverse order initially. – Ben Hocking Jun 19 '11 at 13:29
  • 3
    That's not actually different at all. They are effectively identical. – Puppy Jun 19 '11 at 13:55
  • Can either of you characterize what "effectively identical" means, and hence what "different" looks like, or is it a matter of "I know it when I see it"?. Taking a well-known definition of "the same", the One Definition Rule says they aren't the same (if FactorialC were renamed Factorial and the two appeared in different TUs in the same program). The obvious problem with combining "I know it when I see it" with a categorical statement like "that thing that I'll know when I see, doesn't exist", is that it strongly tempts you into the "no true Scotsman" fallacy. – Steve Jessop Jun 19 '11 at 17:32
1

Let's take the code for factorial from Wikipedia:

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

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

You could implement this alternatively as:

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

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

Or alternatively as:

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

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

template <>
struct Factorial<0> 
{
    enum { value = 1 };
};
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Looks like we had similar ideas! – Ben Hocking Jun 19 '11 at 13:22
  • @Ben Yes but you got yours down a minute before me! – David Heffernan Jun 19 '11 at 13:23
  • 3
    And just as @Ben: These two are functionally *identical*. – Puppy Jun 19 '11 at 13:56
  • @DeadMG: of course they're functionally identical. They compute the same function, that's what functionally identical means. By this definition there's only one way to implement factorial in imperative programming too. I think the questioner is confused, and thinks that "different way to do it" is a well-defined concept. – Steve Jessop Jun 19 '11 at 14:21
  • @Steve, May be my question is not clear, but what I essentially mean is, we cannot have multiple ways in template programming to calculate a constant value. The ways should be completely different. – iammilind Jun 19 '11 at 17:09
  • It's rather dispiriting to answer a question whose rules are arbitrary and exist solely inside the confusion inside OP's head. Please define what you mean by different or move on. – David Heffernan Jun 19 '11 at 17:38
  • @DeadMG: Both David and I changed the code by allowing additional exit points. Additionally, I chose to use a `const long long` where there was an `enum`, and David switched from head recursion to tail recursion (a not-insignificant change). – Ben Hocking Jun 20 '11 at 00:19
1

Generally speaking, the lack of features available in TMP and the relative simplicity of the functionality- if not the way it has to be expressed- means that there's very rarely more than one implementation that differs significantly.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • If you are going to assert uniqueness of implementation, whatever that means, you need a proof. On the other hand, to deny uniqueness all one needs is a counter example. – David Heffernan Jun 19 '11 at 14:35
  • And this is the accepted answer?! I suppose it's the logical conclusion to a bizarre question. – David Heffernan Jun 20 '11 at 06:28
  • @David Hefferman: I never said that *all* TMP constructs have only one way, and I definitely stated "significantly". I said that it was very rare that the differences were significant. – Puppy Jun 20 '11 at 08:37
  • I would think it would be easy enough to construct significantly different implementations for any TMP construct. It's true that such constructions would always be ridiculously artificial and bizarre, but so what? – David Heffernan Jun 20 '11 at 08:43