1

Consider the following classes, where the first is templated. Both are meant to hold a numeric value.

template<typename T>
struct foo
{
    foo(T val) : val{ val } {}
    T val;
};

struct bar
{
    bar(double val) : val{ val } {}
    double val;
};

I want to define a way to add these classes together to get a new one with a different value.

template<typename T>
foo<T> operator+(foo<T> a, foo<T> b)
{
    return foo<T>(a.val + b.val);
}

bar operator+(bar a, bar b)
{
    return bar(a.val + b.val);
}

When I use these operators with implicit conversion, the operator using object of type foo doesn't use the implicit conversion on the double value to apply my overloaded operator, even though it can do it for the non-template class. The result is that there is no operator matching the types in that expression.

int main()
{
    foo<double> foo_value(11.0);
    bar bar_value(11.0);

    foo<double> ev1 = foo_value + 1.0; // no operator matches these operands
    bar ev2 = bar_value + 1.0;
}

Does the operator have to be explicitly instantiated first? If so, a) how does that look like, and b) why isn't the instantiation done implicitly, if it can be done when initializing an object of type foo<double>?

If the standard doesn't support any sort of resolution without explicitly casting 1.0 to a value of type foo<double>, I presume the only other possibility is defining operator overloads for each type I want to use like that (for both lhs and rhs)?

Riddick
  • 319
  • 3
  • 15
  • `bar_value` and `1.0` are different types and will not match a single `T` and `1.0` No implicit conversions prevents a lot of grief in template programming. – doug May 22 '19 at 18:04
  • There's no problem with the statement `bar ev2 = bar_value + 1.0`; implicit conversion is used to match my defined operator. That's not the case with `foo ev2 = foo_value + 1.0` even though the implicit conversion seems obvious. – Riddick May 22 '19 at 18:06

1 Answers1

2

The thing you have to remember about templates is that they will not do a conversion for you. All they do is try and figure out the types things are, and if that jives with template parameters, then it will stamp out the function and call it.

In you case when you do

foo_value + 1.0

the compiler goes okay, lets see if we have any operator + that will work for this. It finds

template<typename T>
foo<T> operator+(foo<T> a, foo<T> b)
{
    return foo<T>(a.val + b.val);
}

and then it tries to figure out what T is so it can stamp out a concrete function. It looks at foo_value, sees it is a foo<double> so it says for the first parameter T needs to be a double. Then it looks at 1.0 and goes okay, I have a double and that is when you run into a problem. The compiler can't deduce what T should be for b because it expects a foo<some_type>, but got a double instead. Because it can't deduce a type, your code fails to compile.

In order to get the behavior you want you would need to add

template<typename T>
foo<T> operator+(foo<T> a, T b)
{
    return foo<T>(a.val + b);
}

Which lets you add a T to a foo<T>, or better yet

template<typename T, typename U>
foo<T> operator+(foo<T> a, U b)
{
    return foo<T>(a.val + b);
}

Which lets you add anything to a foo<T> (foo<double> + int for instance where the first version would not allow that)

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • I tried the latter, and if you define the template like that for both lhs and rhs, the compiler won't know which operator it should use, and it will be an ambiguous call. – Riddick May 22 '19 at 18:08
  • 1
    @Riddick I'd have to see that code to tell you why it didn't work. It works just fine [here](http://coliru.stacked-crooked.com/a/64d53b1a298c0642); – NathanOliver May 22 '19 at 18:11
  • I figured out what happened when I got an ambiguous call: I am asking for the parameter as the parent class type (not shown in the question). So I'm assuming since it doesn't fit the arguments precisely, adding the two additional overloads introduces ambiguity when I pass child classes. When I cast to parent, it works. – Riddick May 22 '19 at 18:23
  • @Riddick Sounds plausible. Hope this helps you. – NathanOliver May 22 '19 at 18:25
  • Is there any practical reason it doesn't attempt to apply implicit conversions in the template case? The way I reason, if it can match object `bar` to a double _and_ I can create a variable with `foo foo_value(1.0)` (and have it correctly match the template), it seems within reach to implicitly convert the argument to the right type too. Would that make function matching too greedy? – Riddick May 22 '19 at 20:56
  • 1
    @Riddick The practical reason is there could be any any number of things it could do a conversion to. Lets say you have two types (`A` and `B`) that can convert to each other, and you pass them to a function template that only takes a single type. Does the compile convert `A` to a `B`, or does it convert `B` to an `A`? Trying to solve that problem, and all of the other edge cases, would make the rules for templates and order of magnitude more complex then they are already, and they are pretty complex as is. – NathanOliver May 22 '19 at 21:06
  • Could I ask you in chat about the larger problem I'm trying to resolve? – Riddick May 22 '19 at 21:08
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/193795/discussion-between-nathanoliver-and-riddick). – NathanOliver May 22 '19 at 21:09