Reputation: 171
I want to write a specialization for a template function, where the type for which it is specialized is itself a templated type. (I am using C++11 or higher.)
In the example code below, I have the generic function convertTo
and a working specialization for int
, allowing me to use convertTo<int>(s)
(as shown). But I cannot figure out how to write specializations for, e.g., std::set<T>
. This is what I tried:
#include <string>
#include <sstream>
#include <set>
#include <unordered_set>
using namespace std;
// generic version
template<class T> T convertTo(const char* str) {
T output;
stringstream ss(str, stringstream::in);
ss >> output;
return output;
}
// specialization for int. works.
template <>
int convertTo<int>(const char* str) {
return atoi(str);
}
template <>
template<class T> set<T> convertTo<set<T>>(const char* str) {
set<T> S;
// TODO split str by comma, convertTo<T>(each element) and put into S
return S;
}
template <>
template<class T> unordered_set<T> convertTo<unordered_set<T>>(const char* str) {
unordered_set<T> S;
// TODO split str by comma, convertTo<T>(each element) and put into S
return S;
}
int main() {
float f = convertTo<float>("3.141");
int i = convertTo<int>("123");
set<int> os = convertTo<set<int>>("9,8,7,6");
unordered_set<int> os = convertTo<unordered_set<int>>("9,8,7,6");
return 0;
}
With g++ 6.3.0 I get the error message:
too many template parameter lists in declaration of ‘std::set<T> convertTo(const char*)’
So I tried to comment out the lines template<>
above the attempted specializations, but then I get:
non-class, non-variable partial specialization ‘convertTo<std::set<T, std::less<_Key>, std::allocator<_CharT> > >’ is not allowed
I don't understand. I didn't intend to write a partial specialization?
I do not want to use template<class Container>
, because I want to be able to write specific code for different container classes. (I need this for other template classes in my code.)
Any advise on how to do this?
Upvotes: 7
Views: 2476
Reputation: 17694
For whatever reason a common answer for this problem is to forward to a static method of an implementation struct. A struct can be partially specialized so this does solve the issue. But it's usually better to use overloading; there are various reasons for that but here I'll simply restrict myself to saying that it's less boilerplate per implementation. You can do it like this:
template <class T>
struct tag{}; // implementation detail
template<class T>
T convertTo(const char* str, tag<T>) {
T output;
stringstream ss(str, stringstream::in);
ss >> output;
return output;
}
int convertTo(const char* str, tag<int>) {
return atoi(str);
}
template<class T>
set<T> convertTo(const char* str, tag<set<T>>) {
set<T> S;
// TODO split str by comma, convertTo<T>(each element) and put into S
return S;
}
template<class T>
unordered_set<T> convertTo(const char* str, tag<unordered_set<T>>) {
unordered_set<T> S;
// TODO split str by comma, convertTo<T>(each element) and put into S
return S;
}
template <class T>
T convertTo(const char * s) {
return convertTo(s, tag<T>{});
};
All of the convertTo
taking two arguments are just overloads now, which is fine. The user facing convertTo
simply calls to the two argument form using our little tag struct to control the dispatch in exactly the same way as partial specialization.
There's lots of other interesting merits to this technique, like the ability to control overload resolution more precisely using additional tag
-like structs and derived-base conversion, the utility of ADL/2-phase lookup, but it's a bit out of scope here.
Upvotes: 4
Reputation: 7443
A partial specialization is when you specify part of the type of the template, but not the whole thing. For example,
template <>
template<class T> set<T> convertTo<set<T>>(const char* str)
would partially specialize for set<T>
if partial function specialization were allowed.
The two main ways of handling this are to instead overload, by removing the template<>
part, or better yet switch to using template class specialization. The problem with overloading is that it looks somewhat different if you are overloading with another template (as with set<T>
) or with a single type (as with int
), and mixing specialization and overload almost certainly doesn't work as you expect.
Therefore, template class specialization is usually the best way to go, and can be done like this:
// Generic version
template <typename T>
class Converter {
public:
T operator() (const char* str) {
T output;
stringstream ss(str, stringstream::in);
ss >> output;
return output;
}
};
template <>
class Converter<int> {
public:
int operator() (const char* str) {
return atoi(str);
}
};
// ...
template <typename T>
T convertTo(const char* str) {
return Converter<T>{}(str);
}
That way you can use any type of specialization you want for the class without issue.
Upvotes: 2