Home > Mobile >  When using function calls to template functions, I am faced with an unexpected matching template fun
When using function calls to template functions, I am faced with an unexpected matching template fun

Time:01-23

For the following code, why is the compiler matching my function calls with (an unexpected) template function:

First of all here are the function templates available:

// First function template
template <typename T, typename U>
auto sub(T x, U y) {
    return x - y;
}

// Second function template
template <typename T>
auto sub(T x, T y) {
    return x - y;
}

Now let's say I begin with a template argument deduction statement:

sub(5.2, 2.1);

The above statement should in turn instantiate a function instance (template function) using the second function template provided above, and would end up looking something like this:

template<>
double sub<double>(double x, double y) {
    return x - y;
}

The next statement wouldn't instantiate a new template function as it would be able to match with the one created previously.

sub<double>(2.3, 234.2);

Now the statement below is where I am having a bit of confusion.

sub<double>(2.3, 234.0f); // Weird one

Although I had provided a single actual type (double), in the template argument, I was faced with a strange result. I purposefully made the second function argument a float, so as to not perfectly match our previously mentioned function instance (template function), and expected floating-point promotion to happen and for the float to be promoted to a double and then match the template function, that isn't what ended up happening... but instead it created a new function instance (template function) using the first function template provided at the top, and ended up looking like this:

template<>
double sub<double, float>(double x, float y) {
    return x - y;
}

What I am confused about is, how I thought that the compiler preferred promotion over something like this, or maybe I am getting it all confused with something else, I am hoping someone could maybe clear it up a bit.

A problem that is also bothering me, is that I don't know how I would then be able to explicitly call the template function I actually want, for example:

sub<double>(2, 2);

What if I wanted it to call the template function with both parameter types being double and just wanted both my arguments to be numerically converted from int to double. By this logic, I wouldn't be able to do that, since the compiler would end up creating a new function instance (template function) that would look something like this:

template<>
double sub<double, int>(double x, int y) {
    return x - y;
}

I must mention I am not talking about how to convert my arguments as I very well know I could use static_cast on them, I'm also not talking about doing something like this:

sub<double, double>(2, 2);

As that would of course, use the first function template to create a new function instance (template function), not use the already available template function (which was created through the use of the second function template). Could someone explain how I would be able to if there really even is a way? All of this, is mostly because I am trying to really understand concepts.

CodePudding user response:

You can supply a partial list of template parameters. For example, if you have

template <typename A, typename B, typename C>
void foo(A, B, C);

then these calls

foo(1,2,3);
foo<int>(1,2,3);
foo<int, double>(1,2,3);
foo<int, double, float>(1,2,3);

are all good.

So sub<double>(2.3, 234.2f); still matches both the first function template and the instantiation of the second template, and the former one is a better match, since it does not require a conversion. The overload resolution only prefers a non-template over a template1 only when both matches are equally good. If the template is a better match, it is used.

It is possible to force the selection of the second template when the two parameters do not have the same type:

auto x = (static_cast<float(*)(float, float)>(sub))(1, 2.0);

but this of course defeats the purpose. To make it convenient to use, you would need to introduce some other selection mechanism, for example

enum class same { type };
template <typename T, same = same::type>
auto sub(T x, T y)...

... sub<double, same::type>(2.3, 234.0f) ...

1 Or rather, a call where a template argument needs to be deduced vs a call where nothing needs to be deduced.

  •  Tags:  
  • Related