Sign in to follow this  

c++ templates??

This topic is 4336 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi. My problem is that I want to make some templates that I can use on both my sprite and tile classes. (In c++). The templates that I want to make are: CSet and CSetManager. CSet will pretty much just have a vector of sprites/tiles, and will have a load function like this: void load(std::string fileName); To read in each tile/sprite it will call their load function (maybe derived from a common ancestor class): void loadFromFile(std::ifstream input); CManager will have a map of CSets, keyed with file names, will load them and remember which ones are loaded and whether they are still being used. My problem is that all the tutorials that I have seen use basic types between the <>. Can I make it so that the template can only be used by classes that have been derived from a common ancestor of my tile and sprite classes (so that I can call their load function)? Or is there another way?

Share this post


Link to post
Share on other sites
As usual I have to disclaim that I'm only an intermediate c++ programmer myself, so anything I say may be completely wrong :-) Fortunately, there are plenty of gurus about to set things straight...

First of all, if you try to construct your templated class with a type that doesn't expose a load() function, a compiler error will be generated. Here's a simple example:
class Yes { public: void Hello() {std::cout << "Hello" << std::endl;} };
class No {};

template < class T > class Maybe { public: Maybe() {T().Hello();} };

// ...

Maybe<Yes> yes; // Fine.
Maybe<No> no; // error: 'class No' has no member named 'Hello'
If you want to take it one step farther though and really only allow a few specific types (not just any type that exposes an appropriate interface), there are other ways to accomplish this. It would take a bit more effort to work up an example for this, but through some sort of trait or tag, you could get the compiler to generate an error if the templated class was not constructed from an approved type.

Share this post


Link to post
Share on other sites
Yes, you can do what you want by taking advantage of SFINAE (substitution failure is not an error). All that means is that when a template is being instatiated, if a parameter type or a return type which is dependent on a template argument cannot be formed, rather than creating an error when being instatiated during lookup, the template is simply not instantiated. This can be extremely important, particularly when there are multiple different templates which can be considered in a given situation, since disambiguation can then occur through exclusion of templates whose parameters or return type can't be formed with the given template arguments, rather than creating an error when the other templates are considered.

You can take advantage of SFINAE in this particular case by incorporating a condition you want into a template with a nested type which exists when the condition is true and does not exist when the condition is false. You then use the nested type in the template as either a parameter type or a return type to the function or type template specialization you are dealing with. In effect, this makes it so that when the condition is false, the template cannot be instatiated meaning you can activate and deactivate function templates and type template specializations based on an arbitrary condition you provide.

If you have boost installed, this is actually extremely easy to accomplish since they have templates specifically for dealing with SFINAE in this manner and they also have metafunctions for checking if a type is a child of another type. So, if you have boost installed, the quick and easy solution checking for only public bases is:


#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_convertible.hpp>

class your_base_type
{
};

template< typename SomeType
, typename Enabler = void
>
class CSet;

template< typename SomeType >
class CSet< SomeType
, typename ::boost::enable_if
<
::boost::is_convertible< SomeType*
, your_base_type*
>
>
::type
>
{
// Put your definition here.
};


Now when you attempt to instantiate CSet with a type that is not publically derived from your base type, you should get a nice error telling you that CSet is not defined for those arguments.

Share this post


Link to post
Share on other sites
Thanks for the answers. I didn't really understand that you could call any old function through the template. But now I get an error from this code:

template <class T>
CObjectSet<T>::~CObjectSet(void)
{
// Delete dynamically allocated vars
std::vector <T*>::iterator itr;
for(itr = bases.begin();itr != bases.end();itr++)
{
delete (*itr);
}
bases.clear();
}

The error just says that it expects a ; before itr.

BTW that SFINAE stuff seems pretty complicated. Are there any good tutorials about it?

Share this post


Link to post
Share on other sites
Quote:
The error just says that it expects a ; before itr.

Short answer is you need the keyword typename before std::vector <T*>::iterator itr;

Long answer is because the compiler doesn't know what T might be, it cannot guarenttee that std::vector <T*>::iterator is actually a type (or that it even exists). The typename keyword tells the compiler to treat it as a type.

Here's an example of typename as well as SFINAE:


// Some user-defined class
class MyClass { };

namespace std
{
// Specialisation of std::vector for MyClass
template<> class vector<MyClass>
{
public:
typedef int test_type;
void iterator() {}
};
}

template<typename T>
void func(const std::vector<T>& vec)
{
// What's iterator? Is it a type (when T is int)
// or is it a function (when T is MyClass)?
std::vector<T>::iterator it = vec.begin();

// Ahh...we're expecting a type
//typename std::vector<T>::iterator it = vec.begin();
}

// SFINAE
// This code will not produce any errors if vector<T> doesn't
// have a type test_type, it will simply be ignored
template<typename T>
typename std::vector<T>::test_type func()
{
return 0;
}



Share this post


Link to post
Share on other sites
Quote:
Original post by andrew_480
BTW that SFINAE stuff seems pretty complicated. Are there any good tutorials about it?


Not that I know of. Luckily, it's not too complex of a concept to grasp, it's just that given the context that we are currently dealing with, it seems more complex than it actually is. In fact, it's very likely that you assumed such behavior existed in simpler cases intuitively without even realizing it.

Quote:
Original post by joanusdmentia

// SFINAE
// This code will not produce any errors if vector<T> doesn't
// have a type test_type, it will simply be ignored
template<typename T>
typename std::vector<T>::test_type func()
{
return 0;
}


Not exactly. That code does not show SFINAE since you never write any code where the template is to be considered for instantiation. If C++ produced error upon substitution failure, the code you posted would still compile perfectly fine without error since nothing is being substituted. You have to actually call an instantiation of the template or have an instantiation considered in order for SFINAE to come into play.

Perhaps most importantly, SFINAE is necessary when two or more templates would seemingly produce ambiguity upon insantiation, but in actuality, just one instantiation makes sense because the template argument can only be fully substituted into the parameter type/return type of one of the considered instantiations.

An example of where this may come up could be when writing templates for dealing with multiple datatypes, but whose result type is determined by a nested type of one of the template arguments whose name is different depending on the type of the argument passed.


struct a
{
typedef void type1;
};

struct b
{
typedef void type2;
};


template< typename Type >
typename Type::type2 func( Type ) {}

template< typename Type >
typename Type::type1 func( Type ) {}

int main()
{
func( a() );
}



Think about what happens when the above function is called. Without SFINAE, your compiler would have a couple of errors here -- one because, upon consideration, the second template would have error (typename Type::type2 does not exist when Type is a), and the second error would be that your call is ambiguous since both instantiations would just take any copied object as a parameter.

However, because of SFINAE, when the templates are considered, if their parameter types and return type cannot be determined given the template argument, rather than having error the template is just ignored as a possibility for instantiation. So, since typename Type::type2 doesn't exist when Type is "a," that template is ignored and the other template is considered. Since that template's parameter list and return type can be constructed with Type being "a," it is a valid candidate for the call. Finally, since it is the only valid template to be instantiated in this context, there is no ambiguity and the call can be made successfully.

So, after understanding that, you may realize that you can enable and disable templates based off of any arbitrary condition you can think of as long as you can make that condition depend on one of the template arguments and have the condition be what makes the substitution of the template arguments fail! An easy way to do this is to make a template type which takes a bool as an argument and defines a nested type if the condition is true and does not define a nested type if the condition is false. Then, use that nested type as a parameter type or return type of the function template or when specializing one of your type templates.

For example:


#include <iostream>

template< bool enable >
struct enabler
{
typedef void type;
};


template<>
struct enabler< false >
{
};

template< typename Type >
typename enabler< sizeof( Type ) < 4 >::type
func()
{
::std::cout << "size is less than 4" << ::std::endl;
}

template< typename Type >
typename enabler< sizeof( Type ) >= 4 >::type
func()
{
::std::cout << "size is greater than or equal to 4" << ::std::endl;
}

int main()
{
func< char >();
func< char[5] >();
}



So, in that example we have the template "enabler" which just takes a bool as an argument. If the argument is true, we define the nested type, otherwise we do not. Then, we instantiate that template and use its nested type as the return type of the function template. If after instantiation the nested type doesn't exist (which happens when the condition is false), then the function template isn't a candidate for the call. In the above example I just check the size of the type and enable one template if the type is less than a certain size and enable a different template if the type is greater than or equal to that size. This way, the functions are chosen based on a condition affected by the size of the type and there is no ambiguity.

Note that since i defined enabler's nested type to be void, the instantiations of the function templates have the return type void -- a more generic solution would be to have the enabler type have two template parameters instead of one, with one of the arguments directly or indirectly determining what the nested type is typedef'd to.

The same logic can be used with template type specializations to enable and disable specializations based on an arbitrary condition (using the same enable template I created in the first example):

template< typename Type
, typename DummyType = void
>
struct type;

template< typename Type >
struct type< Type
, typename enabler< sizeof( Type ) < 4 >::type
>
{
};

template< typename Type >
struct type< Type
, typename enabler< sizeof( Type ) >= 4 >::type
>
{
};

int main()
{
type< char > foo;
type< char[5] > bar;
}



Similar to the function template, the specializations are only considered candidates if the arguments can be substituted. Using the same condition as before, the specializations become candidates depending on the size of the type. Finally, since the type of the nested type upon successful substitution matches the type being passed as a template argument (the DummyType with the default argument of "Type"), that specialization is chosen. Since there is no remaining ambiguity, the template can be successfully instantiated.

So, given the relatively simple concept of SFINAE, you can enable and disable template definitions with arbitrarily complex conditions.

Again, if you have Boost installed, I highly recommend using its enable_if utilities along with its assortment of useful type traits and metafunctions to make working with more complex conditions easier.

Share this post


Link to post
Share on other sites
Quote:
Original post by Polymorphic OOP
Not exactly. That code does not show SFINAE since you never write any code where the template is to be considered for instantiation. If C++ produced error upon substitution failure, the code you posted would still compile perfectly fine without error since nothing is being substituted. You have to actually call an instantiation of the template or have an instantiation considered in order for SFINAE to come into play.


That was meant to be implied, but you're right it is something I should've stated explicitly. Of course, your full explanation is far better than my little attempt at an example [smile]

Share this post


Link to post
Share on other sites

This topic is 4336 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

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