Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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? I.e. 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).

like image 853
Nir Friedman Avatar asked May 17 '16 00:05

Nir Friedman


People also ask

Why function template partial specialization is not allowed?

Function partial specialization is not yet allowed as per the standard. In the example, you are actually overloading & not specializing the max<T1,T2> function. In the case of a function templates, only full specialization is allowed by the C++ standard, -- excluding the compiler extensions!

What is difference between overloaded function and function template?

What is the difference between function overloading and templates? Both function overloading and templates are examples of polymorphism features of OOP. Function overloading is used when multiple functions do quite similar (not identical) operations, templates are used when multiple functions do identical operations.

What is the advantage of using template over function overloading?

the advantage of templates in a situation where you want to do the same set of operations on many different data types, is that the compiler will handle for you at compile time any possible new type you may create in the future that uses the templated function.

Can template function be overloaded?

You may overload a function template either by a non-template function or by another function template. The function call f(1, 2) could match the argument types of both the template function and the non-template function.


1 Answers

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

  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.

EDIT: adding @Nir option 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.

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

EDIT: adding @Nir option 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. 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.

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

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

like image 52
Amir Kirsh Avatar answered Sep 25 '22 01:09

Amir Kirsh