模板化函数模板特化

Templated function template specialization

我想为模板函数编写专门化,其中专门化的类型本身就是模板化类型。 (我使用的是 C++11 或更高版本。)

在下面的示例代码中,我有通用函数 convertToint 的工作特化,允许我使用 convertTo<int>(s)(如图所示)。但是我无法弄清楚如何为 std::set<T> 编写专业化。这是我试过的:

#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;
}

使用 g++ 6.3.0 我收到错误消息:

 too many template parameter lists in declaration of ‘std::set<T> convertTo(const char*)’

所以我试图注释掉尝试的专业化上方的行 template<>,但后来我得到:

non-class, non-variable partial specialization ‘convertTo<std::set<T, std::less<_Key>, std::allocator<_CharT> > >’ is not allowed

我不明白。我没打算写偏专业?

我不想使用template<class Container>,因为我希望能够为不同的容器编写特定的代码类。 (我的代码中的其他模板 类 需要这个。)

关于如何做到这一点有什么建议吗?

部分特化是指您指定模板类型的一部分,而不是全部。例如,

template <>
template<class T> set<T> convertTo<set<T>>(const char* str)
如果允许部分函数专门化,

将部分专门化 set<T>

处理这个问题的两种主要方法是通过删除 template<> 部分来代替重载,或者更好的是切换到使用模板 class 专业化。重载的问题在于,如果您使用另一个模板(如 set<T>)或单一类型(如 int)重载,它看起来会有些不同,并且混合专业化和重载几乎肯定不会没有如你所愿。

因此,模板 class 专业化通常是最好的方法,可以这样做:

// 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);
}

这样您就可以毫无问题地为 class 使用您想要的任何类型的专业化。

无论出于何种原因,此问题的常见答案是转发到实现结构的静态方法。一个结构可以部分特化,所以这确实解决了这个问题。但通常使用重载会更好;这有多种原因,但在这里我只想说每个实现的样板文件更少。你可以这样做:

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>{});
};

所有带两个参数的 convertTo 现在只是重载,这很好。面向 convertTo 的用户只需使用我们的小标记结构调用两个参数形式,以与部分特化完全相同的方式控制分派。

这项技术还有许多其他有趣的优点,例如使用额外的 tag 类结构和派生基转换更精确地控制重载决策的能力,ADL/2-phase 查找的实用程序,但有点超出范围。