0

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.

Community
  • 1
  • 1
Didii
  • 1,194
  • 12
  • 36

2 Answers2

2

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.

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • Sorry for the late answer. The problem apparently is that for some reason default template arguments are not supported in Visual Studios 2012. So while your answer is correct, it did not resolve my problem. – Didii Feb 16 '14 at 12:45
  • @Didii if you can upgrade to the Visual Studio 2012 November CTP or to Visual Studio 2013, default function template arguments are supported. – TemplateRex Feb 16 '14 at 15:27
0

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();
}
  • Seems logical, but isn't there any way around it then? All information was given to the function, so it should be possible. – Didii Feb 11 '14 at 03:42