简体   繁体   中英

Partially Specialized Structs vs Overloaded function template

As we know, function templates cannot be partially specialized in 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).

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).

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?

Herb Sutter has a famous blog post about avoiding function template specialization. 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 .

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

(emphasis added).

is NOT an option as . 不是一个选项,因为(See SO threads on that here , here and here ).

  1. this can work, . 这可以起作用,
    However, it doesn't always work well as we will see below.

  2. this is the straightforward alternative for not having template function specialization. 这是没有模板函数特化的直接替代方法。

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

  1. 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在评论中提出的并在下面给出,可以实现模板函数重载,但是在调用方需要一些繁琐的语法,见下文。

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. 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:

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

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. 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.


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).

See Code: http://coliru.stacked-crooked.com/a/65891b9a6d89e982

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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