Itachi Uchiwa
Itachi Uchiwa

Reputation: 3164

function templates overloading versus fully-specializing

Hello I have this example:

#include <iostream>

template <typename T>
int compare(T const&, T const&);

template <typename T>
int compare(T*, T*);

template <>
int compare(char const * const&, char const* const&);

template <unsigned N, unsigned M>
int compare(char const (&)[N], char const(&)[M]);

template <unsigned N>
int compare(char const (&)[N], char const(&)[N]);


template <typename T>
int compare(T const& lhs, T const& rhs){
    std::cout << "compare(T const&, T const&)\n";

    if(std::less<T>()(lhs, rhs))
        return -1;
    if(std::less<T>()(rhs, lhs))
        return 1;
    return 0;
}

template <typename T>
int compare(T* p1, T* p2){
    std::cout << "compare(T*, T*)\n";
    if( std::less<T>()(*p1, *p2) )
        return -1;
    if( std::less<T>()(*p2, *p1) )
        return 1;
    return 0;
}

template <>
int compare(char const * const& p1, char const* const& p2){
    std::cout << "compare(char const * const &, char const * const &)\n";
    return strcmp(p1, p2);
}

template <unsigned N, unsigned M>
int compare(char const (&ra)[N], char const(&rb)[M]){
    std::cout << "compare(char const(&)[N], char const(&)[M])\n";
    return strcmp(ra, rb);
}

template <unsigned N>
int compare(char const (&ra)[N], char const(&rb)[N]){
    std::cout << "compare(char const(&)[N], char const(&)[N])\n";
    return strcmp(ra, rb);
}

int main(){

    int a = 10, b = 57;
    char const* const cp1 = "Hello";
    char const* const cp2 = "World";

    std::cout << compare(a, b) << '\n';
    std::cout << compare(&a, &b) << '\n';
    std::cout << compare(cp1, cp2) << '\n';
    // std::cout << compare("Hi", "Hi") << '\n'; // error: ambiguous
    // std::cout << compare("Hi", "World!") << '\n'; // error: ambiguous

    cout << '\n';
}

Update:

I've manged to make it work by only changing the signature of the version taking two pointers to the parameter type: compare(T*, T*) to :

template <typename T>
int compare(T const * const&, T const* const&);

Now It works fine so can you explain why?

Upvotes: 0

Views: 134

Answers (2)

Aconcagua
Aconcagua

Reputation: 25526

Why the call to compare passing two arrays of of constant characters is ambiguous even the arrays of different lengths?

Because both of

template <>
int compare(T*, T*);

template <unsigned N, unsigned M>
int compare(char const (&)[N], char const(&)[M]);

match in both cases, and in second case the third overload joins in as well (you might have specialised as template <unsigned N> compare<N, N>(...) to avoid ambiguity at least between those latter two.

Why also passing compare(cp1, cp2); invokes compare(T*, T*) and not compare(char const* const&, char const * const&)?

On selection of the right function template first overload resolution occurs and only when the right overload is selected template specialisations are considered. You now provide two template overloads of compare function:

template <typename T>
int compare(T const&, T const&);

template <typename T>
int compare(T*, T*);

Of these two, the pointer template is a better match, so this one is selected.

template <>
int compare(char const * const&, char const* const&);

is a specialisation of the overload that got discarded, though, and thus has been eliminated as candidate already before. 1)

By accepting pointers by value (char const* px) you'd have specialised the the other base template and thus would see the desired result.

Does my order of declaring the function templates here affects which function is preferred?

No. All overloads that are known at the time of calling the function are considered, no matter in which order they have been declared. If that wasn't the case it would be nearly impossible (at least only with significant research work) to predict which overload gets called if these are imported (possibly indirectly!) via different headers.

Edit (considering updated question):

template <typename T>
int compare(T const&, T const&);

template <typename T>
int compare(T const * const&, T const* const&);

template <>
int compare(char const * const&, char const* const&);

char const* still matches better T const* const& than T const&, so still second overload will be selected. If you now compare the involved signatures then your change provoked, though, the (actually unchanged) specialisation getting a specialisation of this second overload (char const* is closer to T const* const& than to T const&, just as was already during overload resolution).

As now the specialisation refers to the overload already having been selected, you now get the desired/expected result.

Side note: Leaving the original base template (T*) and changing the specialisation to

template <>
int compare(char const*, char const*);

would also have provoked the specialisation being a closer match to second overload – and this change would have been more conclusive as there's no reason for accepting a pointer by const reference – if not optimised away anyway, this is just yet another (needless) level of indirection (not so for non-const pointers, in which case you could apply a change to the pointer variable being passed to the function itself).

1)Adopted from Frank's answer which got deleted in the meanwhile.

Upvotes: 1

user12002570
user12002570

Reputation: 1

Question 1

Why the call to compare passing two arrays of of constant characters is ambiguous even the arrays of different lengths?

Answer 1

This is because when you write compare("Hi", "World!"); there are two version that can be used(equally good). First version is

template <typename T>
int compare(T*, T*);

And the second version is:

template <unsigned N, unsigned M>
int compare(char const (&ra)[N], char const(&rb)[M]);

Important Note

Note that the version:

template <>
int compare(char const * const& p1, char const* const& p2);

is a specialization and so it does not take part in overloading.To be more specific remember that:

Specializations instantiate a template; they do not overload it. As a result, specializations do not affect function matching.

Question 2

Why also passing compare(cp1, cp2); invokes compare(T*, T*) and not compare(char const* const&, char const * const&)?

Answer 2

This is because the version:

template <typename T>
int compare(T*, T*);

will be chosen for "all pointers" and more importantly it can take part in overloading while the version

template <>
int compare(char const * const& p1, char const* const& p2);

is a specialization and hence does not take part in overloading.

Question 3

Does my order of declaring the function templates here affects which function is preferred?

Answer 3

No

Upvotes: 2

Related Questions