繁体   English   中英

部分专业结构vs重载功能模板

[英]Partially Specialized Structs vs Overloaded function template

众所周知,函数模板不能部分专门用于C ++。 当您在概念上尝试实现此目标时,您可以使用两种可能的解决方案。 其中一个是使用带有静态函数的结构,可选地用模板函数包装,如下所示:

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

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

bar这里是可选的,你可以直接使用struct的静态成员(尽管你必须明确指定所有参数)。

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

template <class T, class U>
void func(T t, const U& u)
{
    std::cerr << "func general\n";

}
template <class T>
void func(T t, const double& u)
{
    std::cerr << "func specialized\n";
}

对我来说,似乎第二种方法更可取。 对于初学者来说,它更加冗长,而且对于意图更加清晰(我们正在编写函数,所以让我们使用函数而不是无意义的包装器结构)。 此外,您可以使用一些很好的技巧来控制重载分辨率。 例如,您可以在继承层次结构中包含非模板化“标记”参数,并使用隐式转换来控制函数的优先级。 每当你在重载中具体指定一个类型时,你也会得到隐式转换,如果你不喜欢这种行为,你可以在你的重载上使用enable_if来阻止它(让你回到与结构相提并论)。

是否有理由偏爱部分专业结构? 这些原因有多普遍? 即哪个应该是你的“默认”? 如果您:a)计划自己实现所有特化,而b)这是否用作用户可以注入自己行为的自定义点,这会有所不同吗?

Herb Sutter有一篇关于避免功能模板专业化的着名博文。 在其中,他还建议(接近结尾)偏爱部分专门的结构来重载功能模板,但他似乎没有给出任何具体的理由: http//www.gotw.ca/publications/mill17.htm

道德#2:如果你正在编写一个函数库模板,宁愿把它写成一个永远不应该专门化或过载的单个函数模板

(重点补充)。

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

模板函数特化:不是一个选项,因为模板函数不能部分专门化 (参见此处此处此处的 SO线程)。

  1. 简单的重载:这可以起作用, 正如提问和演示的问题
    但是,它并不总是很好,我们将在下面看到。

  2. 使用仿函数类部分特化:这是没有模板函数特化的直接替代方法。

  3. along with template functions overloading: this approach can be selected when the simple template overloading doesn't work, see below. 使用以及模板函数重载:当简单模板重载不起作用时,可以选择此方法,请参见下文。

编辑:添加@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();
}

we can do: 或者,使用我们可以:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM