Am I the only one who has always been puzzled by templates?

Pages: 12
rapidcoder wrote:
they are just fancy macros and nothing more. When you think of them that way, they are pretty damn simple

If you think of them that way, you're missing the point: templates access, examine, and modify types, they are a programmable plugin framework for the C++ type system. The preprocessor macros manipulate source code tokens, they have no access to the type system. They can't do anything we commonly use templates for - type transformations, specializations, argument deduction, static OOP, overload set manipulation, not to mention anything advanced, like policies or expression template libraries.
I didn't say preprocessor macros. I said macros. The term "macro" is a general term, not unique to C nor C++ preprocessor.


The preprocessor macros manipulate source code tokens, they have no access to the type system. They can't do anything we commonly use templates for - type transformations, specializations, argument deduction, static OOP, overload set manipulation, not to mention anything advanced, like policies or expression template libraries.


Macros can be used to do most of those and even more, maybe without the (limited) type inference feature and specializations and with less readable syntax. Templates work exactly same way - by substituting tokens. Template expansion is implemented at AST level, so it is *syntactically* safe, but it does not have access to the type-system and it doesn't do any typechecking. Typechecking is done later, after expansion.

When the compiler encounters this piece of code:

1
2
template <typename T>
void f(T sth) { sth.doIt() }


it only checks syntax. No semantic analisys, no code generation is done at this point. It is just a bunch of tokens. And then it performs some copy-paste / find-and-replace-all on it.
Last edited on
"Tree" is a general term not unique to computing, but if you say "tree" here, most people will not assume you're referring to vegetable life forms.
closed account (o1vk4iN6)
^ lol

Yah so I was trying to do a binary search on a tree but i couldn't reach a leaf and ended up falling down getting a nasty booboo.

1
2
template <typename T>
void f(T sth) { sth.doIt() }


it only checks syntax. No semantic analisys, no code generation is done at this point. It is just a bunch of tokens. And then it performs some copy-paste / find-and-replace-all on it.


So ? That by no means limits anything...

If you expect a certain type you can easily add it, it'd be no different than what Java does with generics:

1
2
3
4
5
6
template <typename T>
void f(T s)
{
    Sth* sth = &s;
    sth.doIt(); // it's a god damn evaluated miracle
}


Last edited on
I as of yet have not used a template because it is still a bit tricky for me. But I think they are useful or will be for me one day.
@xerzi: ┬┐why don't simply ask for a `Sth*'?
Also, ->
closed account (o1vk4iN6)
Cause you can still do stuff with T ?
The simplest example of a template is this:
1
2
3
4
5
template <class T>
T max(T a, T b)
{
  return (a < b) ? b : a; 
}


Functionally, it's almost the same as this:
#define max(a,b) (((a)<(b))?(b):(a))

It's almost the same in that both return the max and both are somewhat weakly typed. However, there are reasons why the templated version is safter than the define version. Take this for example:
int i = max( a, b++);
If we use the templated function, b is certainly incremented once. In the #define, b might be incremented twice because it's direct text replacement.

There are also much more complicated ways to use templates. I like to use std::enable_if to make templated functions only available to certain types or data, or sizes. Specialization is also a good use here. In fact templates are so powerful that I've seen code which fools your compiler into creating a list of prime numbers during compile time with templates.

Edit: Went to search for a TMP prime calculator. The first google result was this with an answer from someone named Cubbi 4 years ago. Is this our Cubbi? Now we know what you look like!
http://answers.yahoo.com/question/index?qid=20100510032851AAubEzZ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
using namespace std;

// primality test
template <int p, int i>
class is_prime {
public:
  enum { prim = (p==2) || (p%i && is_prime< (i>2?p:0), i-1>::prim ) };
};
template<> class is_prime<0,0> { public: enum {prim=1}; };
template<> class is_prime<0,1> { public: enum {prim=1}; };

// loop to print N prime numbers greater or equal to i
template <int i, int N>
class Prime_print {
public:
  Prime_print<i+1, N - is_prime<i,i-1>::prim> a;
  enum { prim = is_prime<i,i-1>::prim };
  void f() { if(prim) cout << i << endl; a.f(); }
};
template<int i> class Prime_print<i, 0> { public: enum {prim=0}; void f() {} };

int main()
{
  const int N = 10;
  Prime_print<2, N> a;
  a.f();
}
Last edited on
So you need an object that can be referenced by a `Sth*', and you pretend to execute operations that are not part of the `Sth' interface, or in any common part of the inheritance tree
Stewbond wrote:
Is this our Cubbi? Now we know what you look like!

I was pretty drunk in that pic (gotta be, to post to Y!A).. Go with the Stackoverflow profile..
@Stewbond
We know what he looks like anyways. He has a link to his stackoverflow account that has a pic of him in front of a boat marina by the looks of it.

http://stackoverflow.com/users/273767/cubbi

So ? That by no means limits anything...


It doesn't let me write generic code and be sure it is type-correct before I instantiate all the types I plan to use (and you know, I might not know this at this point, because as a library writer I want my code to be as generic as possible). So at the point of writing template, the code-checking support I get from C++ compiler is similar to the support I get from Python interpreter (except the latter is way faster). I don't know if my code is really correct, until I ship it to the customer and he tries to compile his code with my library. This is completely different guarantee (or lack of) than I get from Haskell, OCaml or even Java.

But there is also another thing - e.g. you can't make a template like this usable:

1
2
template <typename A, typename B>
B transform(A a) { ... }


Technically it would be ok, but the user would have to play guessgame on what the type B is (probably by examining a lengthy error message). There are languages that allow for specifying what type B should be produced from type A and this information is visible at the signature-level, so you don't need to inspect the actual implementation.

And C++ typesystem doesn't get variance right, e.g. collection<A> is not a subtype of collection<B> if A is a subtype of B. Java/C# didn't get this right, too.
Last edited on
I know feeding this troll is pointless, but..

rapidcoder wrote:
You can't make a template like this usable:
template <typename A, typename B>
B transform(A a) { ... }


Plenty of people find
1
2
template<typename Target, typename Source>
Target lexical_cast(const Source& arg);
rather usable.
I believe rapidcoder is talking about the lack of type inference in templates.

collection<A> is not a subtype of collection<B> if A is a subtype of B.
I don't see anything wrong with that. There's a difference between "not getting right" and "not wanting".
Completely not helping the point of this thread, but I remember reading some code where someone used compilation errors with templates to do the old 'foobarbaz' program.
Topic archived. No new replies allowed.
Pages: 12