Sign in to follow this  
dudedbz1

(macros + functions) && (templates) [C++]

Recommended Posts

Ok, I have two questions. The first: I know about templated classes:
template<class T> class foo { T member; };
But is it possible to just have a templated function? Like this:
template<class T> T add(T x, T y) {return x + y;}
The second question: Whats the difference between a normal function and a macro?
//The macro:
#define ADD(x, y) ((x) + (y))

//The function:
int Add(int x, int y) {return x + y;}

My guess would be that the macro can work with any type, but wouldnt a template fix that? That is, if the answer to my first question is 'yes'.

Share this post


Link to post
Share on other sites
Yes, you can have a templated member function, and the syntax looks pretty much like you have. A macro is different from a function in that it does preprocessor text replacement rather compiling a function. If you have your add macro and the following code:

c = Add(a, b);

It changes the code to:

c = a + b;

Before the compiler ever sees the code. To the compiler Add() doesn't exist at all.

Share this post


Link to post
Share on other sites
Quote:
Original post by dudedbz1
is it possible to just have a templated function? Like this:

template<class T> T add(T x, T y) {return x + y;}

Certainly.

Quote:
The second question:

Whats the difference between a normal function and a macro?
*** Source Snippet Removed ***
My guess would be that the macro can work with any type, but wouldnt a template fix that? That is, if the answer to my first question is 'yes'.

A template could be written to emulate that functionality. MACROs can be tricky to use and setup in certain situations, as can templates, but templates have a number of advantages: type safety, scope obedience, flexibility, etc. ... I try and stay away from MACROs myself.

A great book for learning about templates is C++ Templates: A Complete Guide. Highly recommend it.

Share this post


Link to post
Share on other sites
Quote:
Original post by dudedbz1
Ok, I have two questions. The first:

I know about templated classes:

template<class T> class foo { T member; };

But is it possible to just have a templated function? Like this:

template<class T> T add(T x, T y) {return x + y;}


The second question:

Whats the difference between a normal function and a macro?
*** Source Snippet Removed ***
My guess would be that the macro can work with any type, but wouldnt a template fix that? That is, if the answer to my first question is 'yes'.


macros can be a pain because their arguments dont always woek as you intend.

the classic example is

#define SQUARE(X) ( (X) * (X) )

what if you do this:

int other = SQUARE(i++);

it gets inlined into code as

int other = ( (i++) * (i++) );

which increments i twice, but is not immediatly obvious.

templated functions dont have such hidden problems.

the other main difference is that such a templated function is translated at compile time. so it obeys the other rules of the language( it can be part of a namespace etc... ). in c++ the preferred way to have "macro-like" functionality is to used templated functions.

Share this post


Link to post
Share on other sites
There is some differences between them, lets consider these add functions:


#define Add1(x,y) (x+y)
int Add2(int x,int y)
{
return x+y;
}
inline int Add3(int x,int y)
{
return x+y;
}
template<typename T>
T Add4(T x,T y)
{
return x+y;
}
template<typename T>
inline T Add5(T x,T y)
{
return x+y;
}
template<typename T,typename T2>
T Add6(T x,T2 y)
{
return x+y;
}
template<typename T,typename T2>
inline T Add7(T x,T2 y)
{
return x+y;
}
template<typename T,typename T2>
T Add8(T x,T2 y)
{
return x+static_cast<T>(y);
}
template<typename T,typename T2>
inline T Add9(T x,T2 y)
{
return x+static_cast<T>(y);
}



Ok we have 9 Add functions, they all add 2 types. The first is the most simple, just a preprocessor function, the preprocessor should be seperate from the C++ compiler so first the preprocessor runs the code and generates code. So imagine this code:

void Foo()
{
Add1(5,3);
}


The preprocessor will start by generating this code:

void Foo()
{
5 + 3 ;
}



The problem with this is that it is not typesafe.

The second and third add function is a standard function taking an int. So this restricts the program to only pass integers or at least requires them to convert to integer, so if you have a short (and sizeof(short)!=sizeof(int) which it normally is) then the short will just gets converted without telling you and this could give performance problems if done a lot, from short to integer ain't very bad though. The difference between the second and third is that the third is "inline" which means the compiler tries to put the code "in the line it was called from", so it will try to generate the same code as the preprocessor function. The second is not inlined which means that the program needs to jump to the function and get back again which probably takes much longer than the actual addition, so this is not how you should do it. Note that inline is just a suggestion so the compiler can choose for itself, but any decent compiler should inline both the second and third add function since it is so small.

The fourth and fifth is the same as the second and third, just templated so it can take more types and if you pass another type like a short you don't need to convert it to an integer since it will just perform a short addition.

The sixth and seventh is a little tricky, they take too different (they can also be equal) types which means you can pass a float and a double for example and you don't need to convert one to the other (unless the addition requires it). It only works if we have an addition operator taking T and T2 or we have an implicit conversion from T2 to T.

The eighth and nineth is just like the sixth and seventh they just tries to convert from T2 to T explicitely, so it will probably work with more types, but can be a bit slower since if the types aren't equal we will do the conversion even though an addition operator can take T and T2 we will also convert even though there might be a loss of data, or precision.

We could also have created add functions returning T2 instead of T, but that would just be too complicated. We could also make a T3 which were the return type.

Share this post


Link to post
Share on other sites
Macros is known to have nasty side effects (for beginners anyway),
aswell as making debugging harder.

If you are using anything "bigger" that a primitive datatype
as the T argument, you should also pass the arguments by referance for better efficiency/speed

template<class T> T add(const T& x, const T& y) {return x + y;}

Share this post


Link to post
Share on other sites
Quote:
Original post by pulpfist
Macros is known to have nasty side effects (for beginners anyway),
aswell as making debugging harder.

If you are using anything "bigger" that a primitive datatype
as the T argument, you should also pass the arguments by referance for better efficiency/speed

template<class T> T add(const T& x, const T& y) {return x + y;}


I have created a little class to solve this problem, I'm still a beginner with template metaprogramming and this is the first thing I try to do so it might not be very good, but I have created a structure with can give you type "const T&" if sizeof(T)>sizeof(T&) and type "const T" if sizeof(T)<=sizeof(T&).

The code for the class is:


/*

//////////////////////////////////////////////////
//////////////////////////////////////////////////
//////////////////////////////////////////////////
// DOESN'T WORK, SEE POLYMORPHIC OOP'S POST
// I will leave it here though so people can see
// what he is talking about, but don't use it.
//////////////////////////////////////////////////
//////////////////////////////////////////////////
//////////////////////////////////////////////////
template<typename T>
struct ReadOnlyValue
{
// Get the size of a reference to T and the type itself so we can later compare and
// choose what to use.
enum
{
ReferenceSize = sizeof(const T&),
ValueSize = sizeof(const T)
};

template<bool UseValueType>
struct _Type;

template<>
struct _Type<true>
{
typedef const T Type;
};
template<>
struct _Type<false>
{
typedef const T& Type;
};

typedef typename _Type<ReferenceSize>=ValueSize>::Type Type;
};
*/







EDIT: But this stuff is in most cases not worth doing.

[Edited by - CTar on November 5, 2005 3:58:23 PM]

Share this post


Link to post
Share on other sites
Quote:
Original post by Roboguy
I would like to point out that macros don't inherently have the problems mentioned. C and C++ macros do, but (for example) Scheme's hygienic macros don't.

Of course, the OP isn't refering to Scheme. So your comment is completely pointless.

CM

Share this post


Link to post
Share on other sites
Quote:
Original post by Conner McCloud
Quote:
Original post by Roboguy
I would like to point out that macros don't inherently have the problems mentioned. C and C++ macros do, but (for example) Scheme's hygienic macros don't.

Of course, the OP isn't refering to Scheme. So your comment is completely pointless.

CM


No it isn't, I wanted to make sure that they didn't think that macros in general have those problems. That way, if they ever use Scheme, they won't avoid macros for those reasons without really learning anything about them.

Share this post


Link to post
Share on other sites
Quote:
Original post by Roboguy
No it isn't, I wanted to make sure that they didn't think that macros in general have those problems. That way, if they ever use Scheme, they won't avoid macros for those reasons without really learning anything about them.

Most language specific advice can be rendered pointless if considering the right alternative language. Disclaimers to that affect are just pointless noise.

CM

Share this post


Link to post
Share on other sites
@ Roboguy :

I am not using Scheme, as you can see, I said C++.

@ Pulpfist :

Your last reply is completely useless, and your other reply was sort of useless. All you said is that macros have side effects and you told me to use reference which I know a lot about, and wasnt talking about.

@ Everyone else :

Thanks, now I get it. So, this:

#define Add(x, y) ((x) + (y))


is (?) completely (?) identical to this:

template<class T>
inline T Add(T x, T y)
{
return x + y;
}


Is that true?

P.S. Does anyone know of any good tutorials on templates? They seem very useful and interesting.

Share this post


Link to post
Share on other sites
Quote:
Original post by Conner McCloud
Quote:
Original post by Roboguy
No it isn't, I wanted to make sure that they didn't think that macros in general have those problems. That way, if they ever use Scheme, they won't avoid macros for those reasons without really learning anything about them.

Most language specific advice can be rendered pointless if considering the right alternative language. Disclaimers to that affect are just pointless noise.

CM


The argument that macros are bad for the reasons mentioned are probably more language specific than what I said.

Quote:
Original post by dudedbz1
@ Roboguy :

I am not using Scheme, as you can see, I said C++.


Did you read what I said? I never said it was advice for what you were trying to do at the moment, but for people who will or are use Scheme. I never thought that you were using Scheme.


Anyway, I'm through derailing the thread. Sorry [smile].

Share this post


Link to post
Share on other sites
Quote:
Original post by CTar
I have created a structure with can give you type "const T&" if sizeof(T)>sizeof(T&) and type "const T" if sizeof(T)<=sizeof(T&).


There are a couple of problems with this. For one, sizeof( T& ) is required to yield the size of T, not the sizeof a reference, so sizeof(T) will always equal sizeof(T&) in C++.

Secondly, it's not just the size of the type which should affect your decision, it's more whether or not the type also has nontrivial copy construction and destruction. For instance, a smart pointer may be the size of a regular pointer, however copying and destroying smart pointers can be costly, since it can imply things like incrementing and decrementing reference counts, etc. Because of this, it can be more efficient to pass by reference. As well, what about types which can't be copied? In such a case, a reference has to be be used regardless of its size and destructor, etc.

Anyway, the simplest rule to follow is that if you are unsure which to use, use a reference to const, since reference arguments are extremely easily optimized away with inlining.

Alternatively, if you are really interested in attempting to determine which method to use, you should check out Boost.CallTraits, which accomplishes the type of logic similar to what you described.

Share this post


Link to post
Share on other sites
Quote:

Thanks, now I get it. So, this:
*** Source Snippet Removed ***
is (?) completely (?) identical to this:
*** Source Snippet Removed ***
Is that true?

P.S. Does anyone know of any good tutorials on templates? They seem very useful and interesting.


No they are far from equal, first inline is only a suggestion, so the compiler might choose to not inline the template function though it would be very bad if it did. There is also the difference that the preprocessor macro is a replacement of text instead of a function, which could introduce many problems (I can't see any in an add function though) look at rip-off's SQUARE example to see what I mean. Also there is the problem of scope which rip-off also mentioned, but ain't a problem with an add function. The template function is also type-safe meaning that it will say that you passed something wrong if you pass anything, but a type, or a type which don't have add. Lastly the template function can't take to different parameters unless one the second can be converted to the first, this can be solved by a function like this:

template<typename T,typename T2,typename RT>
RT Add(T X,T2 Y)
{
return (X+Y);
}


I'm pretty certain this can give you problems with some compilers, which can't identify what to set some of the template arguments to.

Share this post


Link to post
Share on other sites
Quote:
Original post by Polymorphic OOP
There are a couple of problems with this. For one, sizeof( T& ) is required to yield the size of T, not the sizeof a reference, so sizeof(T) will always equal sizeof(T&) in C++.

Secondly, it's not just the size of the type which should affect your decision, it's more whether or not the type also has nontrivial copy construction and destruction. For instance, a smart pointer may be the size of a regular pointer, however copying and destroying smart pointers can be costly, since it can imply things like incrementing and decrementing reference counts, etc. Because of this, it can be more efficient to pass by reference. As well, what about types which can't be copied? In such a case, a reference has to be be used regardless of its size and destructor, etc.

Anyway, the simplest rule to follow is that if you are unsure which to use, use a reference to const, since reference arguments are extremely easily optimized away with inlining.

Alternatively, if you are really interested in attempting to determine which method to use, you should check out Boost.CallTraits, which accomplishes the type of logic similar to what you described.


Thanks for the feedback, I know that you should think of other stuff than size, but for the purposes I'm using it the size is the most important. Anyway a type like this shouldn't be used before profiling so you can just see with what type there is a problem and provide a pass-by-value or pass-by-reference explicit template declaration depending on what the problem was.

Also I second your suggestion to use a const reference if you aren't sure what to use.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this