-
Template specialisation of template class member function
What I am trying to do is get a piece of code like the following working:
Code:
template <typename T>
class BufferClass
{
public:
template <typename Iter_>
void assign(Iter_ first, Iter_ last)
{
//Do something
}
//But if it is a list containing any element type then...
template <>
void assign<typename std::list<T>::iterator >(
typename std::list<T>::iterator first,
typename std::list<T>::iterator last)
{
//Do something else
}
//...
private:
//.....
};
Now clearly, the above is wrong, because you cannot specialise the member function without fully specialising the enclosing class (that said, it rather annoyingly compiles under VS2005!:( ). The problem is, I have no idea what T will be, so, I cannot fully specialise the enclosing class (short of writing far more specialisations than I care to write - there has to be a better solution that this... I hope).
I have thought about creating a partially specialised nested class - as far as I know it's ok to partially specialise a member class without specialising the enclosing class. But, then again, that doesn't really help since it's the member function I'm trying to specialise.
Anyway, any ideas on how to create a 'legal' version of the above code would be much appreciated.
At the end of the day I don't want to have to use function overloading. i.e.
Code:
template <typename Iter_>
void assign(Iter_ first, Iter_ last)
{
//Do something
}
void assign(std::list<char>::iterator first, std::list<char>::iterator last)
{
//Do something
}
void assign(std::list<unsigned char>::iterator first,
std::list<unsigned char>::iterator last)
{
//Do something
}
void assign(std::list<short>::iterator first, std::list<short>::iterator last)
{
//Do something
}
void assign(std::list<unsigned short>::iterator first,
std::list<unsigned short>::iterator last)
{
//Do something
}
// etc ...
-
Re: Template specialisation of template class member function
You just need to create another template class with list as parameter in angle bracket.
-
Re: Template specialisation of template class member function
-
Re: Template specialisation of template class member function
I once had a similar problem and solved it by providing a non-member friend function (which can be specialized).
-
Re: Template specialisation of template class member function
What is it about std::list that makes it special?
If the assign function changes on iterator_category then could you specialise on that?
Where Iter_::iterator_category is bidirectional_iterator_tag or random_access_iterator_tag etc.
-
Re: Template specialisation of template class member function
Quote:
Originally Posted by GNiewerth
I once had a similar problem and solved it by providing a non-member friend function (which can be specialized).
Thanks for the suggestion - I had a play with this idea and it might just be my incompetence, but for this particular case I can't think of a way to do it without the requirement of partial specialisation of a non-member function, which is currently illegal. :(
-
Re: Template specialisation of template class member function
Quote:
Originally Posted by JohnW@Wessex
What is it about std::list that makes it special?
If the assign function changes on iterator_category then could you specialise on that?
Where Iter_::iterator_category is bidirectional_iterator_tag or random_access_iterator_tag etc.
It is more to do with the iterator_catgory than list per se... the problem is, specialising on iterator_category would then break the assign for raw pointers, but I guess that is easier to fix. I'll have a play with this idea - thanks for the suggestion. :)
-
Re: Template specialisation of template class member function
Quote:
the problem is, specialising on iterator_category would then break the assign for raw pointers
You could handle all types of iterator (including pointers) by using std::iterator_traits<Iter_>::iterator_category
-
Re: Template specialisation of template class member function
Quote:
Originally Posted by JohnW@Wessex
You could handle all types of iterator (including pointers) by using std::iterator_traits<Iter_>::iterator_category
I realised that when I was looking at the specialisation. :) I now have a solution...
-
Re: Template specialisation of template class member function
Ok, it's a bit convoluted but it works. If you think any of it is a bad idea, or could be done in a better way then let me know. :)
I defined a helper class:
Code:
template <typename IteratorCategory_, typename Buffer_>
class AssignmentHelper
{
public:
template <typename Iter_>
void assign(Iter_ first, Iter_ last, Buffer_& buffer)
{
buffer.assignUsingIteration(first, last);
}
};
which I partially specialised as follows:
Code:
template <typename Buffer_>
class AssignmentHelper<std::random_access_iterator_tag, Buffer_>
{
public:
template <typename Iter_>
void assign(Iter_ first, Iter_ last, Buffer_& buffer)
{
buffer.assignUsingMemcpy(first, last);
}
};
I then created a function to test the value_type of my BufferClass:
Code:
//A minor modification of how Andrei Alexandrescu achieves
//primative data type recognition in his book "Modern C++ Design"
template<typename T>
bool isPrimitive(T)
{
typedef char Small;
typedef char (&Big)[2];
Small Test(long double);
Big Test(...);
return sizeof( Test(T()) ) == sizeof(Small) ;
}
Finally, I redefined my BufferClass as follows:
Code:
template <typename T>
class BufferClass
{
public:
typedef T value_type;
template <typename Iter_>
void assign(Iter_ first, Iter_ last)
{
AssignmentHelper<typename std::iterator_traits<Iter_>::iterator_category, BufferClass<T> > assignHelper;
assignHelper.assign(first, last, *this);
}
template <typename Iter_>
void assignUsingMemcpy(Iter_ first, Iter_ last)
{
if(!isPrimitive(value_type()))
{
std::cout << "Buffer<T> value_type is non-POD: ";
assignUsingIteration(first, last);
}
else
{
std::cout << "Can use memcpy" << std::endl;
}
}
template <typename Iter_>
void assignUsingIteration(Iter_ first, Iter_ last)
{
std::cout << "Can't use memcpy" << std::endl;
}
//...
private:
//...
};
Now, when I run it with:
Code:
int main()
{
std::vector<double> vec;
std::list<double> lst;
BufferClass<double> test;
std::cout << "Test with std::list:\n -> ";
test.assign(lst.begin(),lst.end());
std::cout << "Test with std::vector:\n -> ";
test.assign(vec.begin(),vec.end());
std::vector< std::vector<double> > vecNonPod;
BufferClass<std::vector<double> > nonPodTest;
std::cout << "Test with std::vector with non-POD value_type:\n -> ";
nonPodTest.assign(vecNonPod.begin(),vecNonPod.end());
}
I get the following heart warming output:
Code:
Test with std::list:
-> Can't use memcpy
Test with std::vector:
-> Can use memcpy
Test with std::vector with non-POD value_type:
-> Buffer<T> value_type is non-POD: Can't use memcpy
I would appreciate any feedback.
-
Re: Template specialisation of template class member function
I thought at first that the helper functions could be specialised directly.
A bit of experimentation showed that partial function template specialisation is not legal C++. :(
-
Re: Template specialisation of template class member function
That point gave me a real headache when attempting to come up with a solution. In the end, I had to make the specialisation at the class level. :(
The funny thing is, although it is illegal to specialise a member function without fully specialising the enclosing class, and it is illegal to fully specialise a nested class without fully specialising the enclosing class, it is not illegal to partially specialise a nested class even if the enclosing class is not fully specialised.
With that thought, I think I'll nest the AssigmentHelper class and its partial specialisation within the buffer and make its definition private. :)
-
Re: Template specialisation of template class member function
You may want to check out boost::type_traits for doing this sort of thing.
It is now part of TR1. for example
You can also look at the source code of an STL implementation like STLport to see boost::type_traits in use to do this sort of optimization.
-
Re: Template specialisation of template class member function
Thanks souldog, but unfortunately boost is not an option, so I was trying to figure out how to do it myself.
-
Re: Template specialisation of template class member function
No idea what is talking here.
What is tag and what is benefits code in this way rather than create different assign function in a class ?
Thanks for your help.
-
Re: Template specialisation of template class member function
Each type of STL container iterator has certain properties. std::vector's iterators are 'random access' (you can move the iterator any number of positions at one go) while std:list's are bidirectional (foreward/backward, one position at a time). Pointers are also random access iterators.
When writing a template that accepts iterators you may need to know what type of iterator you are dealing with, as you may be able to write a more efficient algorithm with, say, random access iterators. The iterator_traits template class allows you to discover this information at compile time and create specialisations of templated classes by checking the iterator's 'tag'.
-
Re: Template specialisation of template class member function
Speed.
One memcpy of 100 elements is probably faster than a loop with 100 assignments.
What about the type traits Souldog mentioned? Using traits will speed up your assign method because it´s evaluated at compile time and you don´t need the is_primitive call (though I must admit I don´t fully understand what is_primitive does).
One drawback of using type_traits is that you have to provide them for every type you´re going to use.
-
Re: Template specialisation of template class member function
Quote:
Originally Posted by GNiewerth
One drawback of using type_traits is that you have to provide them for every type you´re going to use.
Not necessarily. You can define a default template for something like Is_Pod where the falue is 'false', then specialise the template for POD types. When you use it for a new class or structure type it will automatically acquire the 'false' value.
-
Re: Template specialisation of template class member function
Quote:
Originally Posted by JohnW@Wessex
Not necessarily. You can define a default template for something like Is_Pod where the falue is 'false', then specialise the template for POD types. When you use it for a new class or structure type it will automatically acquire the 'false' value.
Hehe, yes, I´m aware of that. But to use the power of memcpy you have to provide a type_traits specialization for your class.
-
Re: Template specialisation of template class member function
Quote:
Originally Posted by PredicateNormative
Ok, it's a bit convoluted but it works. If you think any of it is a bad idea, or could be done in a better way then let me know. :)
I defined a helper class:
Code:
template <typename IteratorCategory_, typename Buffer_>
class AssignmentHelper
{
public:
template <typename Iter_>
void assign(Iter_ first, Iter_ last, Buffer_& buffer)
{
buffer.assignUsingIteration(first, last);
}
};
which I partially specialised as follows:
Code:
template <typename Buffer_>
class AssignmentHelper<std::random_access_iterator_tag, Buffer_>
{
public:
template <typename Iter_>
void assign(Iter_ first, Iter_ last, Buffer_& buffer)
{
buffer.assignUsingMemcpy(first, last);
}
};
I then created a function to test the value_type of my BufferClass:
Code:
//A minor modification of how Andrei Alexandrescu achieves
//primative data type recognition in his book "Modern C++ Design"
template<typename T>
bool isPrimitive(T)
{
typedef char Small;
typedef char (&Big)[2];
Small Test(long double);
Big Test(...);
return sizeof( Test(T()) ) == sizeof(Small) ;
}
Finally, I redefined my BufferClass as follows:
Code:
template <typename T>
class BufferClass
{
public:
typedef T value_type;
template <typename Iter_>
void assign(Iter_ first, Iter_ last)
{
AssignmentHelper<typename std::iterator_traits<Iter_>::iterator_category, BufferClass<T> > assignHelper;
assignHelper.assign(first, last, *this);
}
template <typename Iter_>
void assignUsingMemcpy(Iter_ first, Iter_ last)
{
if(!isPrimitive(value_type()))
{
std::cout << "Buffer<T> value_type is non-POD: ";
assignUsingIteration(first, last);
}
else
{
std::cout << "Can use memcpy" << std::endl;
}
}
template <typename Iter_>
void assignUsingIteration(Iter_ first, Iter_ last)
{
std::cout << "Can't use memcpy" << std::endl;
}
//...
private:
//...
};
Now, when I run it with:
Code:
int main()
{
std::vector<double> vec;
std::list<double> lst;
BufferClass<double> test;
std::cout << "Test with std::list:\n -> ";
test.assign(lst.begin(),lst.end());
std::cout << "Test with std::vector:\n -> ";
test.assign(vec.begin(),vec.end());
std::vector< std::vector<double> > vecNonPod;
BufferClass<std::vector<double> > nonPodTest;
std::cout << "Test with std::vector with non-POD value_type:\n -> ";
nonPodTest.assign(vecNonPod.begin(),vecNonPod.end());
}
I get the following heart warming output:
Code:
Test with std::list:
-> Can't use memcpy
Test with std::vector:
-> Can use memcpy
Test with std::vector with non-POD value_type:
-> Buffer<T> value_type is non-POD: Can't use memcpy
I would appreciate any feedback.
I do not think it is safe to assume that all random access iterators will iterate over contiguous memory, just that they provide constant-time access to any element in the collection. If a random access iterator operates on a single channel of an interlaced buffer, for instance, memcpy would not work correctly. I think your best bet would be the specialize for those iterators you know to be contiguous, such vector::iterator and string::iterator. This could even be wrapped up tidily in something like an is_contiguous type trait.
-
Re: Template specialisation of template class member function
In fact std::string::iterator IS an example of an iterator which is not guaranteed to iterate over contiguous memory.
-
Re: Template specialisation of template class member function
Quote:
Originally Posted by Zaccheus
In fact std::string::iterator IS an example of an iterator which is not guaranteed to iterate over contiguous memory.
Well, it's a debatable point, depending on what kind of guarantee you're looking for. The current standard does not require that strings be contiguous, but future standards will and most if not all current implementations do.