简体   繁体   English

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

[英]Partially Specialized Structs vs Overloaded function template

As we know, function templates cannot be partially specialized in C++. 众所周知,函数模板不能部分专门用于C ++。 When you are conceptually trying to achieve this, there are two possible solutions you can use. 当您在概念上尝试实现此目标时,您可以使用两种可能的解决方案。 One of them is to use structs with a static function, optionally wrapped with a template function, like so: 其中一个是使用带有静态函数的结构,可选地用模板函数包装,如下所示:

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 here is optional, you can if you like just use the struct's static member directly (though you will have to then explicitly specify all arguments). bar这里是可选的,你可以直接使用struct的静态成员(尽管你必须明确指定所有参数)。

The other approach is just to overload function templates: 另一种方法是重载函数模板:

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

To me, it seems like the second approach is preferable. 对我来说,似乎第二种方法更可取。 For starters it is much less verbose, and far clearer with regards to intent (we're writing functions, so let's use functions instead of pointless wrapper structs). 对于初学者来说,它更加冗长,而且对于意图更加清晰(我们正在编写函数,所以让我们使用函数而不是无意义的包装器结构)。 Also, there are some nice tricks you can play with functions to control overload resolution. 此外,您可以使用一些很好的技巧来控制重载分辨率。 For instance, you can have non-templated "tag" arguments in an inheritance hierarchy, and use implicit conversion to control priority of functions. 例如,您可以在继承层次结构中包含非模板化“标记”参数,并使用隐式转换来控制函数的优先级。 You also get implicit conversions anytime you concretely specify a type in an overload, and if you don't like that behavior you can just use enable_if on your overload to prevent it (bringing you back to par with structs). 每当你在重载中具体指定一个类型时,你也会得到隐式转换,如果你不喜欢这种行为,你可以在你的重载上使用enable_if来阻止它(让你回到与结构相提并论)。

Are there reasons to prefer the partially specialized structs? 是否有理由偏爱部分专业结构? How general are these reasons? 这些原因有多普遍? Ie which should be your "default"? 即哪个应该是你的“默认”? Does this differ if you: a) plan to implement all specializations yourself, versus b) this is used as a customization point where users can inject their own behavior? 如果您:a)计划自己实现所有特化,而b)这是否用作用户可以注入自己行为的自定义点,这会有所不同吗?

Herb Sutter has a famous blog post about avoiding function template specialization. Herb Sutter有一篇关于避免功能模板专业化的着名博文。 In it, he also recommends (right near the end) preferring partially specialized structs to overloaded function templates, but he doesn't seem to give any concrete reasons: http://www.gotw.ca/publications/mill17.htm . 在其中,他还建议(接近结尾)偏爱部分专门的结构来重载功能模板,但他似乎没有给出任何具体的理由: http//www.gotw.ca/publications/mill17.htm

Moral #2: If you're writing a function base template, prefer to write it as a single function template that should never be specialized or overloaded 道德#2:如果你正在编写一个函数库模板,宁愿把它写成一个永远不应该专门化或过载的单个函数模板

(emphasis added). (重点补充)。

Let's list first the options for creating several variants of the same template method: 让我们先列出创建同一模板方法的几个变体的选项:

Template function specialization: is NOT an option as template functions cannot be partially specialized . 模板函数特化:不是一个选项,因为模板函数不能部分专门化 (See SO threads on that here , here and here ). (参见此处此处此处的 SO线程)。

  1. Simple overloading: this can work, as the question mentions and demonstrates . 简单的重载:这可以起作用, 正如提问和演示的问题
    However, it doesn't always work well as we will see below. 但是,它并不总是很好,我们将在下面看到。

  2. Using functor class partial specialization: this is the straightforward alternative for not having template function specialization. 使用仿函数类部分特化:这是没有模板函数特化的直接替代方法。

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

EDIT: adding @Nir option 4 编辑:添加@Nir选项4

  1. Using function parameters which are based on a template: this approach, as suggested by Nir in the comments and presented below, enables template function overloading, but requires some cumbersome syntax on the caller side, see below. 使用基于模板的函数参数:这个方法,如Nir在评论中提出的并在下面给出,可以实现模板函数重载,但是在调用方需要一些繁琐的语法,见下文。

--- END of EDIT --- ---编辑结束---

The question presents a case where template function overloading works fine, when the template parameter is deduced from the call. 该问题提出了一种情况,当模板参数从调用中推导出来时,模板函数重载工作正常。 However in cases where the call to the template function is providing the template parameters directly, and there is a need to match the implementation based on relations or conditions on the template parameters, overloading cannot assist anymore. 但是,如果对模板函数的调用直接提供模板参数,并且需要根据模板参数的关系或条件匹配实现,则重载不再有用。

Consider the following: 考虑以下:

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

Though val1 and val2 are KNOWN at compilation, there is no way to partial specialize the case where we KNOW at compile time that they are the same. 虽然在编译时val1和val2是已知的,但是我们无法在编译时知道它们是相同的情况下部分特殊化。 Function overloading doesn't help in this case, there is no overloading for the case that two non-type template parameters have the same value. 在这种情况下,函数重载没有帮助,在两个非类型模板参数具有相同值的情况下没有重载。

With class partial specialization we can do: 通过类部分特化,我们可以做到:

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

Or alternatively, with std::enable_if we can do: 或者,使用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;
}

The main for all the options above would look like: 上面所有选项的主要部分如下:

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

EDIT: adding @Nir option 4 编辑:添加@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;
}

The main for this option would look like: 此选项的主要内容如下:

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

I don't see any advantage in option 4's syntax. 我认为选项4的语法没有任何优势。 But one can think otherwise... 但是人们可以不这么认为......

Note the need for a .cpp file in option 4, for the declaration of T foo::val , in all other options everything is suitable for .h files. 注意在选项4中需要一个.cpp文件,对于T foo::val的声明,在所有其他选项中,一切都适用于.h文件。

--- END of EDIT --- ---编辑结束---


To summarize: 总结一下:

Cases where we can earn compile time resolution, based on template meta-programming, partial specialization is required. 基于模板元编程,我们可以获得编译时解析的情况,需要部分专业化。 This can be achieved for functions via class partial specialization or using enable_if (which in turn needs its own class partial specialization for its condition). 这可以通过类部分特化或使用enable_if来实现(对于其条件,它又需要其自己的类部分特化)。

See Code: http://coliru.stacked-crooked.com/a/65891b9a6d89e982 请参阅代码: http//coliru.stacked-crooked.com/a/65891b9a6d89e982

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

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