7

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?

lawilog
  • 173
  • 6
  • 3
    Function templates cannot be partially specialised. Overload instead. – n. m. could be an AI Sep 20 '17 at 15:10
  • 1
    *"I didn't intend to specialize"* - And yet you used specialization syntax `convertTo>` – StoryTeller - Unslander Monica Sep 20 '17 at 15:49
  • Surely this is dupe, there are millions of partially specialize function templates on SO. – Nir Friedman Sep 20 '17 at 16:08
  • I did intend to specialize, but as far as I understand it, "partial template specialization" means just fixing some template arguments, while leaving others templated. So, I think I want to write a full specialization? @n.m.: I think that's what I tried by removing `template<>`. – lawilog Sep 20 '17 at 16:11
  • 1
    @lawilog It's not fully fixed though, `set` still has a free parameter. Partial specialization isn't only fixing down one template parameter while leaving the second free. You can partially specialize a struct with only one template parameter; if you have a primary definition for `T` and a secondary definition for `set`, the second is still a template (and thus, not fully specialized), but it is more specialized than the primary in some cases. Thus, partial specialization. – Nir Friedman Sep 20 '17 at 16:13
  • I see. But I also cannot overload by return type. – lawilog Sep 20 '17 at 16:19

2 Answers2

4

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.

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72
  • 1
    Thank you. Looks nice. In this case I'd name it like convert(x, to); :-) – lawilog Sep 20 '17 at 16:38
  • This is another good solution. I prefer the solution I gave because it doesn't involve adding a dummy parameter, but I'd use overloading if the type appeared in the function arguments naturally. Do note that weird things can happen if you add specialization into the mix, though. – Daniel H Sep 21 '17 at 00:44
2

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.

Daniel H
  • 7,223
  • 2
  • 26
  • 41
  • Pretty well disagree with this, overloading is superior for various reasons, some of which are only sometimes relevant but if nothing else it's less boilerplate per partial specialization. – Nir Friedman Sep 20 '17 at 16:14
  • Thanks. This is how I filled in the `// ...` for me: template struct Converter> { inline set operator() (const char* str) { set S; /* TODO */ return S; } }; – lawilog Sep 20 '17 at 16:32
  • @NirFriedman I prefer this method when the type doesn't appear in the parameters at all because I don't like dummy parameters, but that is also a perfectly good answer. – Daniel H Sep 21 '17 at 00:50