简体   繁体   中英

Function template overloading vs Explicit specialization

I set up two template functions which get sums for different STL containers: one is for list and vector, the other is for the map.

Please see the commented line (1) and (2) of the second template function definition. The commented-out code (=2) also works fine, so I do not know which one is more recommended syntax.

Also, how does each method called (Am I aptly guessed in my comment)? To say (1) is a function template overloading seems not enough because it lacks typename argument after the keyword 'template'. That is, It should be like template<typename T> combined with (1) in order for the method to be called as a function template overloading, I guess. Please give me their right name.

template <typename T> // T : container
double Sum(const T &l) // get Container
{
    double sum = 0;
    T::const_iterator i;
    for (i = l.begin(); i != l.end(); ++i) { sum += *i; }
    return sum;
}

template <> // get container
double Sum(const map<string, double> &m) // (1) function template overloading
// double Sum<map<string, double> >(const map<string, double> &m) // (2) explicit specialization
{
    double sum = 0;
    map<string, double>::const_iterator i; // obtain Iterator from Container
    for (i = m.begin(); i != m.end(); i++) { sum += i->second; } // sum. ('first' is index, 'second' is data)
    return sum;
}

Both are explicit specialization syntax

  • template <> double Sum(const map<string, double> &m);

  • template <> double Sum<map<string, double> >(const map<string, double> &m)

The first one let compiler deduce the parameter, whereas the second, you are explicit.

The second is required when compiler cannot deduced all template parameters as for

template <typename T> std::string getNameType();

template <> std::string getNameType<int>() { return "int"; }

or to disambiguate which template function to specialize

template <typename T> void foo(T);

template <typename T> void foo(T*); // overload, not specialization

//template <> void foo(int*); // Error: do you mean T = int for foo(T*) or T = int* for foo(T)

template <> void foo<int*>(int*); // specialize foo(T)
template <> void foo<int>(int*);  // specialize foo(T*)

It is generally better to use overload instead of specialization for function, so for your example:

template <typename Key>
double Sum(const std::map<Key, double> &m)
{
    double sum = 0;
    for (const auto& p : m) { sum += p.second; }        return sum;
}

For a concrete type, you're probably better off simply defining a non-template overload:

double Sum(const std::map<std::string, double> &m) // (1) function template overloading
// double Sum<map<string, double> >(const map<string, double> &m) // (2) explicit specialization
{
    double sum = 0;
    std::map<std::string, double>::const_iterator i; // obtain Iterator from Container
    for (i = m.begin(); i != m.end(); i++) { sum += i->second; } // sum. ('first' is index, 'second' is data)
    return sum;
}

Templates are more useful for generic functions:

//
// sum any map's arithmetic mapped values
//
template<class K, class V, class C, class A>
typename std::map<K, V, C, A>::mapped_type
Sum(const std::map<K, V, C, A> &m) // (1) function template overloading
{
    using map_type = std::map<K, V, C, A>;
    using mapped_type = typename map_type::mapped_type;
    using const_iterator = typename map_type::const_iterator;
    mapped_type sum = 0;
    const_iterator i; // obtain Iterator from Container
    for (i = m.begin(); i != m.end(); i++) { sum += i->second; } // sum. ('first' is index, 'second' is data)
    return sum;
}

... or to get pedantically generic (c++14)...

namespace notstd {
    template< class... >
    using void_t = void;
}

template< class, class = notstd::void_t<> >
struct supports_mapped_type : std::false_type { };

template< class T >
struct supports_mapped_type<T, notstd::void_t<typename T::mapped_type>> : std::true_type { };

template< class, class = notstd::void_t<> >
struct supports_const_iterator : std::false_type { };

template< class T >
struct supports_const_iterator<T, notstd::void_t<typename T::const_iterator>> : std::true_type { };

template<class T> static constexpr bool IsMaplike = supports_mapped_type<T>() and supports_const_iterator<T>();

template<class MapLike,
std::enable_if_t<
IsMaplike<MapLike> and std::is_arithmetic<typename MapLike::mapped_type>()
>* = nullptr>
typename MapLike::mapped_type
Sum(const MapLike &m) // (1) function template overloading
{
    using map_type = MapLike;
    using mapped_type = typename map_type::mapped_type;
    using const_iterator = typename map_type::const_iterator;
    mapped_type sum = 0;
    const_iterator i; // obtain Iterator from Container
    for (i = m.begin(); i != m.end(); i++) { sum += i->second; } // sum. ('first' is index, 'second' is data)
    return sum;
}

This would now happily sum:

std::unordered_map<std::string, int> , and std::map<std::string, int> ,

but not std::map<int, std::string>

I didn't completely understand your question, as your code seems to be OK, but I'll make an attempt to answer. Function overloading is an approach when you manually write several functions with the same name but different argument types. For example:

double Sum(const std::vector<double>& l) {
    //...
}

double Sum(const std::list<double>& l) {
    //...
}

double Sum(const std::deque<double>& l) {
    //...
}

In your example you wrote a function template:

template <typename T>
double Sum(const T &l) //...

and a template specialization:

template <>
double Sum(const map<string, double> &m) //...

Which is better? It depends on your situation. See, with function overloading you should write the code by yourself, while in case of templates the compiler will do it for you!

For example, your general-case template will work for vector , list , queue , deque and any other compatible container which even may be nonexistent for the moment of template creation. Compiler generates the code only for those types which are used to instantiate the template. If you try to instantiate it with incompatible type, you'll get a compilation error. And only if you instantiate it with map<string, double> (which is invalid for the general case template), compilation will succeed as the specialization will be selected for code generation.

As @RichardHodges mentioned, specialization might be an overkill for your case; non-template overloading should be enough.

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