Search code examples
c++templatesfunction-templates

When do I need to specify the type a template


I've written a simple template to find the smallest number in a list of arguments.

template<typename T>
T smallerList(T a, T b) {
    std::cout << "a= " << a << " b= " << b << std::endl;
    return a < b ? a : b;
}

template<typename T, typename... Rest>
T smallerList(const T& param0, const Rest&... rest) {
    T temp =  smallerList(rest...);
    return param0 < temp ? param0 : temp;
}

int main()
{
       // Works, returns "3"
       std::cout << "Smaller: " << smallerList(4, 5, 6, 3, 7) << std::endl;

       // Sort of works, returns "2". Should be "2.14".
       std::cout << "Smaller: " << smallerList(3.14, 43534, 100.2, 3.13, 2.14) << std::endl; 

}

For some reason the second function call returns 2 instead of 2.14. Why does this happen?

I've printed the intermediate values of the variables and they are correct. It seems like an implicit conversion is happening when smallerList returns.

I was able to solve this by changing the line

T temp =  smallerList(rest...);

into

T temp =  smallerList<T>(rest...);

After this change the function prints 2.14 as expected.

My question: Why do I need to specify the type? I thought that templates functions are "created" for each type that is called?


Solution

  • Ok, I don't know how to really help you, because I don't know what exact logic you want to have. But at you variadic template you allow type mixing, and in your 2nd vector, you pass one integer - 43534, so when 2.14 back propagates when recursion rolls back you get something like

    return (int)(43534 < 2.14? 43534 : 2.14);

    Because 43534 will be param0, and you take return type of param0, and 2.14 gets converted to 2. Next it gets converted back to a float but you don't see it.

    You either need to check whether the types of the parameters are the same, or come up with some logic to promote your arguments. Not that it does work like you expect if you use 43534.0 because it won't be an int anymore.

    EDIT:

    T temp = smallerList<T>(rest...); this does not really help you, it changes the behaviour, forcing a cast on each argument to type of the 1st argument. Well it gets more consistent. But try:

    smallerList(7, 10.5, 10, 3.13, 2.14)
    

    It will break. I am not 100% sure why, but I guess it couldn't match end of recursion, because it would look for smallerList(int, float) and your terminator template won't match.

    You would need something like:

    template<typename T, typename U>
    T smallerList(T a, U b) {
        std::cout << "a= " << a << " b= " << b << std::endl;
        return a < b ? a : b;
    }
    

    It also discards 2nd type, so you will have type conversions, but if your goal is to keep the the type of 1st argument it is consistent.