Partial template specialization by overloading

2019-08-31 01:42发布

问题:

I created a simple round template function with an extra template argument that defines the type the rounded value needs to be casted to before returning.

template <typename T, typename U>
T round(U val) {
    T result;
    if (val >= 0)
        result = (T)(floor(val + (U)(.5)));
    else
        result = (T)(ceil( val - (U)(.5)));
    return result;
}

int a = round<int>(5.5); // = 6
// no compiler warnings

But I also want the possibility to leave the extra template argument so that you don't have to insert the type you already added as an argument.

template <typename T>
T round(T val) {
    return round<T>(val);
}

double b = round(5.5) // = 6.0
// C2668

However, now the compiler complains:

error C2668: ambiguous call to overloaded function

I thought the compiler would always choose the most specific template, which should be the last one. Why is this not the case and are there any workarounds (not specifically for this round function)?


The ambiguous call wasn't pointing to round(5.5) but rather to return round<T>(val);. The answer to this question was thus to rewrite the return value for the overloaded function to

return round<T,T>(val);

which solves the problem.

Thanks to galop1n for the answer to my other question.

回答1:

You are getting an error because return types are not deduced during template argument deduction. Instead they are substituted from the deduced function arguments. Because both overloads have the same arguments deduced, overload resoution is ambiguous which gives a compiler error.

In C++11 you can define a default template parameter for function templates. If you add an extra default function parameter equal to the default return value, you will always get the argument type as return type, unless you explicitly pass a default return value:

#include <iostream>
#include <cmath>
#include <type_traits>

template <typename T, typename Ret = T>
Ret xround(T val, Ret ret = Ret()) {
    return static_cast<Ret>(
        (val >= 0) ?
        floor(val + (T)(.5)) :
        ceil( val - (T)(.5))
    );
}

int main()
{
    auto a = xround(5.5, int()); // = 6
    static_assert(std::is_same<decltype(a), int>::value, "");
    std::cout << a << "\n";       

    auto b = xround(5.5); // = 6.0
    static_assert(std::is_same<decltype(b), double>::value, "");
    std::cout << b << "\n";
}   

Live Example

Note that I used the ternary operator instead of your if-else, and that I renamed the function to xround because in C++11 there already is a round inside <cmath> (which of course you could also use).

Note: the temporary is similar to tag dispatching: it is entirely used to determine the return type and the actual temporary should be optimized away by the compiler.



回答2:

Your issue is not template specialization, but an overload ambiguity.

This is similar:

int fn(int) { return 0; }
// error: new declaration ‘double fn(int)’
// error: ambiguates old declaration ‘int fn(int)’
double fn(int) { return 0; }

Having a template where U has T as default parameter will not do better:

template <typename T, typename U = T>
T fn(U val) {
    return T();
}

int main() {
    // error: no matching function for call to ‘fn(double)’
    // note: template argument deduction/substitution failed:
    double d = fn(1.5); // fn<double>(1.5) will work
}

And a partial specialization is not allowed:

template <typename T, typename U>
T fn(U val) {
    return T();
}

// error: function template partial specialization ‘fn<T, T>’ is not allowed
template <typename T>
T fn<T, T>(T val) {
    return T();
}