c++ template question

Started by
17 comments, last by Polymorphic OOP 19 years, 1 month ago
Quote:Original post by Volte6
Interesting related question:

I thought I would phase out the sprite list, and a sprite list manager ( AKA sprite list list ).

So at first I thought:
  class SpriteManager {     LinkedList< LinkedList<Sprite>> listList;  };

Which in my head sounds like a list of lists... but it doesn't work that way for whatever reason ( Apparently templates are more like macros... they aren't real code ).

Then I got an idea:
  typedef LinkedList<Sprite> SpriteList;  class SpriteManager {    LinkedList<SpriteList> listList;  };

And that compiles... does it look right to you? I define a NEW type "SpriteList" which is just a linked list of sprites.
Then I create a linked list of that new type.


They are real code, and not at all like macros other than that they expand. Your original code will work, you have just encountered one of C++'s odd parsing errors. You need a space between your >>, otherwise the compiler thinks you have a right bit-shift.

class SpriteManager {
LinkedList< LinkedList<Sprite> > listList;
};

will work fine, although the typedef is much nicer.
--Michael Fawcett
Advertisement
Okay so that worked... great! Any Snafu's i should be aware of?

Also, These are expanded at run time or compile time? That's why I thought they were like macros.. I thought they were expanded at compile time.

Additionally, How does my typedef version compare to the single line version? Is there anything I should know about typedef?

Sorry, doing this whole self education thing leaves one with many questions :)
Quote:Original post by Volte6
Also, These are expanded at run time or compile time? That's why I thought they were like macros.. I thought they were expanded at compile time.
Compile time (unlike Java.NET generics, which are expanded at runtime). The main difference is that templates allow for recursion, are type-checked by the compiler, and are part of the language, and not an add-on like the preprocessor.

Templates exist mainly to avoid some of the problems of macros, but macros can do things that templates can't - and the combination of template metaprogramming and preprocessor metaprogramming is incredibly powerful.

Quote:Original post by Volte6
Additionally, How does my typedef version compare to the single line version? Is there anything I should know about typedef?
The typedef version avoids the parsing issue that mfawcett mentioned. When nesting templates (such as vector< vector<int> > The space in the > > is extremely important. Typedes can help avoid little gotchas like this.

Template code has a lot of gotchas. Look at the boost code to see how much effort is put into getting templates to work across multiple compilers. Boost also is a good place to look for ideas on how to push C++ to the very edge of its capability - many of the people involved in Boost are writing the specs for the next revision of C++.
Quote:Original post by jdhardy
Quote:Original post by Volte6
Also, These are expanded at run time or compile time? That's why I thought they were like macros.. I thought they were expanded at compile time.
Compile time (unlike Java.NET generics, which are expanded at runtime). The main difference is that templates allow for recursion, are type-checked by the compiler, and are part of the language, and not an add-on like the preprocessor.

Templates exist mainly to avoid some of the problems of macros, but macros can do things that templates can't - and the combination of template metaprogramming and preprocessor metaprogramming is incredibly powerful.


Yes, yes it is. I feel compelled to share something I put together recently (based on something Fruny provided to me in the channel, which in turn comes from the Boost mailing list or something like that):

// introspect.h -- deep magic that I *mostly* understand.
// Remove the spaces after backslashes if you use this.
#ifndef INTROSPECT_H#define INTROSPECT_Htypedef char (&n)[1];typedef char (&y)[2];template< bool condition, typename T> struct enable_if {};template< typename T > struct enable_if<true,T> { typedef T type; };#define INTROSPECT_IMPL(id, ret, func, cflag, ...) \ template< typename T, ret (T::*)(__VA_ARGS__) cflag> struct has_##id##_pfn {}; \ template< typename T > n has_##id##_x(...); \ template< typename T > y has_##id##_x(int, has_##id##_pfn<T, &T::func >* p = 0); \ template< typename T > struct has_##id { \    enum { value = sizeof(has_##id##_x<T>(0)) == sizeof(y) }; \ }#define INTROSPECT(id, ret, func, ...) INTROSPECT_IMPL(id, ret, func,, __VA_ARGS__)#define INTROSPECT_CONST(id, ret, func, ...) INTROSPECT_IMPL(id, ret, func, const, __VA_ARGS__)#define WHEN_AVAILABLE(id, ret) \ template<typename T> typename enable_if<has_##id<T>::value,ret>::type#define WHEN_UNAVAILABLE(id, ret) \ template<typename T> typename enable_if<!has_##id<T>::value,ret>::type#define WHEN_AVAILABLE_INLINE(id, ret) \ template<typename T> inline typename enable_if<has_##id<T>::value,ret>::type#define WHEN_UNAVAILABLE_INLINE(id, ret) \ template<typename T> inline typename enable_if<!has_##id<T>::value,ret>::type#endif


// A usage example:
#include <algorithm>#include <vector>#include <complex>#include "introspect.h"// This was the nicest interface I could acheive - the first parameter for the// INTROSPECT (or INTROSPECT_CONST) macro is a "cookie" which is associated with// the particular introspection we are doing. In this case, symbol "foo" is// associated with objects which provide the member function "void swap(T)",// where T is the object type (you don't get to change this or specify extra// template parameters unfortunately). The other parameters are of course the// return type, function name (it also works with operators), and argument list// (you can specify 'const's and '&'s and so on in there too). INTROSPECT_CONST// is the same except it looks for const member functions instead. INTROSPECT(foo, void, swap, T);// For objects that provide the member function associated with "foo", this// version of fast_swap is instantiated, and declared inline. It is again// templated on T, and returns void (the second parameter to // WHEN_AVAIABLE_INLINE).WHEN_AVAILABLE_INLINE(foo, void) fast_swap(T& a, T& b) {  a.swap(b);}// For other types, this other version is instantiated. Thus fast_swap will// call an object's own swap() method if it exists, but otherwise default to// std::swap.WHEN_UNAVAILABLE_INLINE(foo, void) fast_swap(T& a, T& b) {  ::std::swap<T>(a,b);}// I also provided plain WHEN_AVAILABLE and WHEN_UNAVAILABLE macros which do// not declare the function inline. The problem is that "inline" has to come// before the return type, but the return type has to be specified in the macro.// Test cases. This is as in the original sample; I suppose it would be more// dramatic to actually initialize some values and output them...int main() {   std::vector<int> vector1, vector2;   std::complex<int> complex1, complex2;   double a,b;   fast_swap(vector1, vector2);   fast_swap(complex1, complex2);   fast_swap(a,b);}


Tested/developed with DJGPP (GXX 3.43).
Quote:Original post by Zahlman
Quote:Original post by jdhardy
Quote:Original post by Volte6
Also, These are expanded at run time or compile time? That's why I thought they were like macros.. I thought they were expanded at compile time.
Compile time (unlike Java.NET generics, which are expanded at runtime). The main difference is that templates allow for recursion, are type-checked by the compiler, and are part of the language, and not an add-on like the preprocessor.

Templates exist mainly to avoid some of the problems of macros, but macros can do things that templates can't - and the combination of template metaprogramming and preprocessor metaprogramming is incredibly powerful.


Yes, yes it is. I feel compelled to share something I put together recently (based on something Fruny provided to me in the channel, which in turn comes from the Boost mailing list or something like that):

// introspect.h -- deep magic that I *mostly* understand.
// Remove the spaces after backslashes if you use this.
*** Source Snippet Removed ***

// A usage example:
*** Source Snippet Removed ***

Tested/developed with DJGPP (GXX 3.43).
Nifty. It looks like it allows the choice of operation based on what's available.

Here's mine, minus a lot of extra scaffolding:
//This is valid C++ code!CREATE(Employees,	((SIN AS int))	((Name AS string))	)CREATE(Tasks,		((Employee_SIN AS int))		((Name AS string))	)cout<<SELECT( (Employees::SIN)(Employees::Name)(Tasks::Name) 			FROM (Employees)(Tasks) 			WHERE Employees::SIN() == Tasks::Employee_SIN()		)<<endl<<endl;
It's part of a relational database engine with the query optimizer implemented at compile-time. Right now it's just a naive join (cross product followed by a selection), but I hope to add some smarts to it.

Without Boost.MPL and Boost.Preprocessor, this wouldn't even be close to possible.
Quote:Original post by Volte6
Okay so that worked... great! Any Snafu's i should be aware of?

Also, These are expanded at run time or compile time? That's why I thought they were like macros.. I thought they were expanded at compile time.

Additionally, How does my typedef version compare to the single line version? Is there anything I should know about typedef?

Sorry, doing this whole self education thing leaves one with many questions :)

They do expand at compile time. That is a HUGE difference from macros which are expanded during preprocessing (i.e. when type information is not present).
--Michael Fawcett
Quote:Original post by jdhardy
Nifty. It looks like it allows the choice of operation based on what's available.


Yup, that's exactly what it does. You write a 'wrapper' function template which check if a class provides an 'optimized' operation (like container::swap vs. std::swap or std::list::sort vs. std::sort) and if so, transparently use it, or default to the 'general but slow' version.

Kudos to Zahlman for doing what I didn't quite feel up to: wrapping it all up in macros. One comment though: Boost's MPL already provides enable_if and types for true/false values.

Quote:Also, These are expanded at run time or compile time? That's why I thought they were like macros.. I thought they were expanded at compile time.


Macros know nothing about your program. They are pure text substitutions. One huge consequence is that macros do not respect scope - templates do.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
Quote:Original post by Fruny
Quote:Original post by jdhardy
Nifty. It looks like it allows the choice of operation based on what's available.


Yup, that's exactly what it does. You write a 'wrapper' function template which check if a class provides an 'optimized' operation (like container::swap vs. std::swap or std::list::sort vs. std::sort) and if so, transparently use it, or default to the 'general but slow' version.
While this is probably a bad example, what does this get you that regular template specialization/function overloading doesn't? One could overload std::swap for the std:: containers (which I imagine they do), but this just may be a case of bad example. [smile]
Quote:Original post by Fruny
One comment though: Boost's MPL already provides enable_if and types for true/false values.

Just noting that enable_if, lazy_enable_if, and the associated disablers aren't in MPL, they're in Boost.Utility.

This topic is closed to new replies.

Advertisement