Home c++ -> template meta programming vs template functions
Reply: 1

c++ -> template meta programming vs template functions

LeastSquaresWonderer
1#
LeastSquaresWonderer Published in 2018-01-12 16:53:06Z

I am learning C++ template meta-programming. I want to know the difference between the following constructs. Suppose the classic example of the factorial.

Example 1

template <int n>
struct factorial
{
  enum { fac = n*factorial<n-1>::fac };
};
factorial<4> ex;

(where we omit the termination conditions for brevity.)

Example 2

template<int n> factorial<n> foo1(){return factorial<n>();}

Example 3

template<int n> int foo2(){return n*foo2<n-1>();}
template<> int foo2<0>(){return 1;}
int ex2=foo2<4>()

Example 4

template<int n>
  int foo3(){
    int k=1;
    for(int i=2;i<=n;i++) k*=i;
    return k;
}
int ex3=foo3<4>();

What are the differences between the 4 examples? In particular, what is done at compile time for each variant?

My Thoughts

It's clear that example 1 is completely compile-time. The second, I think, is also compile-time, but I'm not sure what the compiler does. The third is also at compile time? I'm not certain.

The fourth isn't compile-time. What is done at compile time? Does the compiler create a code-generator for the function, where n is "replaced" by the constant value?

Please correct me, or add to my thoughts.

TriskalJM
2#
TriskalJM Reply to 2018-01-12 19:10:14Z

What are the differences between the 4 examples? In particular, what is done at compile time for each variant?

It's clear that example 1 is completely compile-time.

Quite right: the factorial<4> is a type, and a type is created/computed at compile-time. So the factorial of 4 is computed at compile-time. At run-time, the object ex of type factorial<4> is initialized (can be initialized: the compiler is free do it at compile-time also).

The second, I think, is also compile-time, but I'm not sure what the compiler does.

I don't see the ex1 row.

I suppose

factorial<4> ex1 = foo1<4>();

or, starting from C++11, also

auto ex1 = foo1<4>();

The answer is quite as for case (1): foo<N>() return a factorial<N> that is a type and a type is created/computed compile time; so the factorial of N is computed compile time; run time is called (can be called) the function foo1() and is initialized (can be initialized) the object ex1 of type factorial<4>.

The third is also at compile time? I'm not certain.

No: It returns an int, not a factorial<N>. It's usually computed run-time (even if the compiler is free to compute it at compile-time).

But, starting from C++11, you can write foo2() as a constexpr function, like so:

template<int n>
constexpr int foo2(){return n*foo2<n-1>();}

template<>
constexpr int foo2<0>(){return 1;}

A constexpr function can be computed compile-time or run-time, but you can force its computation at compile-time if you use it for a value that must be known at compile-time.

By example, initializing a constexpr variable:

constexpr int ex2 = foo2<4>();  // factorial computed compile time

or for a size of a C-style array:

int  a[foo2<4>()];  // factorial computed compile time

of for a template argument:

factorial<foo2()> ex;  // factorial of factorial computed compile time

But the constexpr function can be simpler and more flexible. You can write it as an untemplated recursive function as follows [Caution: code not tested]

constexpr int foo2 (int n)
 { return n ? n * foo2(n-1) : 1; }

This way foo2() can compute (at run-time, obviously) factorial values for run-time variables. For example:

for ( auto i = 0 ; i < 5 ; ++i )
   std::cout << i << "! = " << foo2(i) << std::endl;

Observe that foo2() remains usable at compile-time, but the value n must be known at compile-time.

I mean

constexpr int i1 = 4;  // i1 is usable compile time
int i2 = 4;            // i2 isn't usable compile time

int f1 = foo2(4);  // OK: computed run time (f1 isn't constexpr)
int f2 = foo2(i1); // OK: computed run time (f2 isn't constexpr)
int f3 = foo2(i2); // OK: computed run time (+ i2 ins't constexpr)
constexpr f4 = foo2(4);  // OK: computed compile time
constexpr f5 = foo2(i1); // OK: computed compile time
constexpr f6 = foo2(i2); // compilation error! (i2 isn't constexpr)

The fourth isn't compile-time. What is done at compile time? Does the compiler create a code-generator for the function, where n is "replaced" by the constant value?

Right.

But you can define it constexpr (starting from C++14: it's too complex to be a C++11 constexpr function), so it can be computed at compile-time or run-time and must be computed compile-time when the value must be known at compile-time.

constexpr int ex3 = foo3<4>(); // factorial computed compile time

As foo2(), foo3() can be defined constexpr receiving a not-template parameter (I repeat: starting from C++14) [Caution: code not tested]

constexpr int foo3 (int n)
{
   int k = 1;
   for (int i=2 ; i <= n ; i++ )
      k *= i;
   return k;
}

or, in a more compact way:

constexpr int foo3 (int n)
{
   int ret = n ? n : 1;

   while ( --n > 0 )
      ret *= n;

   return ret;
}
You need to login account before you can post.

About| Privacy statement| Terms of Service| Advertising| Contact us| Help| Sitemap|
Processed in 0.351368 second(s) , Gzip On .

© 2016 Powered by mzan.com design MATCHINFO