Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Templated function template specialization

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?

like image 925
lawilog Avatar asked Sep 20 '17 14:09

lawilog


1 Answers

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.

like image 72
Nir Friedman Avatar answered Dec 11 '22 17:12

Nir Friedman