部分专用结构与重载函数模板



正如我们所知,函数模板在C++中不能部分专业化。当你在概念上试图实现这一点时,有两种可能的解决方案可以使用。其中之一是使用带有静态函数的structs,可以选择使用模板函数包装,比如:

template <class T, class U>
struct BarHelper
{
    static void BarHelp(T t, const U& u)
    {
        std::cerr << "bar generaln";
    }
};
template <class T>
struct BarHelper<T, double>
{
    static void BarHelp(T t, const double& u)
    {
        std::cerr << "bar specializedn";
    }
};
template <class T, class U>
void bar(T t, const U& u)
{
    BarHelper<T, U>::BarHelp(t, u);
};

这里的bar是可选的,如果您愿意,可以直接使用结构的静态成员(尽管您必须显式指定所有参数)。

另一种方法只是重载函数模板:

template <class T, class U>
void func(T t, const U& u)
{
    std::cerr << "func generaln";
}
template <class T>
void func(T t, const double& u)
{
    std::cerr << "func specializedn";
}

对我来说,第二种方法似乎更可取。对于初学者来说,它不那么冗长,而且在意图方面要清晰得多(我们正在编写函数,所以让我们使用函数而不是毫无意义的包装结构)。此外,您还可以使用一些控制过载分辨率的函数来玩一些不错的技巧。例如,您可以在继承层次结构中具有非模板化的"标记"参数,并使用隐式转换来控制函数的优先级。当你在重载中具体指定类型时,你也会得到隐式转换,如果你不喜欢这种行为,你可以在重载上使用enable_if来防止它(让你回到结构的水平)。

有没有理由更喜欢部分专业化的结构?这些原因有多普遍?也就是说,哪一个应该是你的"默认"?如果你:a)计划自己实现所有专业化,而b)这是一个自定义点,用户可以在这里注入自己的行为,这会有所不同吗?

赫伯·萨特(HerbSutter)有一篇关于避免函数模板专业化的著名博客文章。在这篇文章中,他还建议(就在最后)更喜欢部分专业化的结构,而不是重载的函数模板,但他似乎没有给出任何具体的理由:http://www.gotw.ca/publications/mill17.htm.

道德#2:如果你正在编写一个函数库模板,更喜欢将其作为一个单一的函数模板来编写,该模板永远不应该是专门化的或重载的

(增加了重点)。

让我们首先列出创建同一模板方法的几个变体的选项:

模板函数专用化:不是一个选项,因为模板函数不能部分专用化。(请参阅此处、此处和此处的SO线程)。

  1. 简单重载:这是可行的,正如问题提到并演示的
    然而,正如我们将在下面看到的那样,它并不总是很好地工作。

  2. 使用函子类部分专门化:这是不具有模板函数专门化的直接替代方案。

  3. std::enable_if与模板函数重载一起使用:当简单的模板重载不起作用时,可以选择这种方法,请参阅下文。

编辑:添加@Nir选项4

  1. 使用基于模板的函数参数:正如Nir在评论中所建议的,这种方法可以实现模板函数重载,但需要调用方使用一些繁琐的语法,请参阅下文

---编辑结束---

该问题提供了一种情况,当从调用中推导出模板参数时,模板函数重载工作正常。然而,在对模板函数的调用直接提供模板参数,并且需要基于模板参数上的关系或条件来匹配实现的情况下,重载不再有帮助。

考虑以下内容:

template <typename T, T val1, T val2>
void isSame1() {
    cout << "val1: " << val1 << ", val2: " << val2 << " are "
         << (val1==val2?" ":"NOT ") << "the same" << endl;
}

虽然val1和val2在编译时是已知的,但在编译时我们知道它们是相同的情况下,没有办法进行部分专门化。函数重载在这种情况下没有帮助,在两个非类型模板参数具有相同值的情况下没有重载。

通过类部分专业化,我们可以做到:

template <typename T, T val1, T val2>
struct IsSameHelper {
    static void isSame() {
        cout << "val1: " << val1 << ", val2: " << val2 << " are NOT the same" << endl;
    }
};
// partial specialization
template <typename T, T val>
struct IsSameHelper<T, val, val> {
    static void isSame() {
        cout << "val1: " << val << ", val2: " << val << " are the same" << endl;
    }
};
template <typename T, T val1, T val2>
void isSame2() {
    IsSameHelper<T, val1, val2>::isSame();
}

或者,使用std::enable_if,我们可以:

template<typename T, T val1, T val2>
struct is_same_value : std::false_type {};
template<typename T, T val>
struct is_same_value<T, val, val> : std::true_type {};
template <typename T, T val1, T val2>
typename std::enable_if<!is_same_value<T, val1, val2>::value, void>::type isSame3() { 
    cout << "val1: " << val1 << ", val2: " << val2 << " are NOT the same" << endl;
}
template <typename T, T val1, T val2>
typename std::enable_if<is_same_value<T, val1, val2>::value, void>::type isSame3() {
    cout << "val1: " << val1 << ", val2: " << val2 << " are the same" << endl;
}

上面所有选项的主要内容如下:

int global1 = 3;
int global2 = 3;
//======================================================
// M A I N
//======================================================
int main() {
    isSame1<int, 3, 4>();
    isSame1<int, 3, 3>();
    isSame1<int*, &global1, &global1>();
    isSame1<int*, &global1, &global2>();
    isSame2<int, 3, 4>();
    isSame2<int, 3, 3>();
    isSame2<int*, &global1, &global1>();
    isSame2<int*, &global1, &global2>();
    isSame3<int, 3, 4>();
    isSame3<int, 3, 3>();
    isSame3<int*, &global1, &global1>();
    isSame3<int*, &global1, &global2>();
}

编辑:添加@Nir选项4

template <class T, T v> struct foo{
    static constexpr T val = v;
};
// in a .cpp
template <class T, T v>
constexpr T foo<T, v>::val; // required for non-integral / non-enum types
template <class T, T v1, T v2> void isSame4(foo<T, v1> f1, foo<T, v2> f2) {
    cout << "val1: " << f1.val << ", val2: " << f2.val << " are NOT the same" << endl;
}
template <class T, T v> void isSame4(foo<T, v> f1, foo<T, v> f2) {
    cout << "val1: " << f1.val << ", val2: " << f2.val << " are the same" << endl;
}

该选项的主要内容如下:

int global1 = 3;
int global2 = 3;
//======================================================
// M A I N
//======================================================
int main() {
    isSame4(foo<int, 4>(), foo<int, 3>());
    isSame4(foo<int, 3>(), foo<int, 3>());
    isSame4(foo<int*, &global1>(), foo<int*, &global1>());
    isSame4(foo<int*, &global1>(), foo<int*, &global2>());
}

我认为选项4的语法没有任何优势。但人们可以不这么想。。。

请注意,在选项4中需要.cpp文件,对于T foo::val的声明,在所有其他选项中,所有内容都适用于.h文件。

---编辑结束---


总结如下:

在我们可以获得编译时分辨率的情况下,基于模板元编程,需要部分专业化。这可以通过类部分专门化或使用enable_if(反过来,它需要自己的类部分专业化来满足其条件)为函数实现。

参见代码:http://coliru.stacked-crooked.com/a/65891b9a6d89e982

最新更新