21

I have a class templated with typename T. It contains a function,

template <typename T, size_t a>
myClass<T,a> operator+(myClass<T,a> lhs, const T& rhs) {
    return lhs += rhs;
}

myClass<T,a> myClass<T,a>::operator+=(const T& rhs) {
    // Do addition, depends on 'a'.
    return *this;
}

When I call this with, for example

myClass<double, 2> myObj_double_2(constructor args);
myObj_double_2 = myObj_double_2 + 5.2;

I have no problem.

If I however call

myObj_double_2 = myObj_double_2 + 5;

Then the compiler gives me a message like - No match for 'operator+' (operand types are 'myClass<double, 2ul>' and 'int'). Candidates are ... note: deduced conflicting types for parameter 'const T' ('double' and 'int').

Can I write the code in some way to allow additional types to be passed that have a conversion to T (since, for example, double(5) is a valid constructor call)?

6
  • try myObj_double_2 = myObj_double_2 + 5.0; Commented Oct 5, 2015 at 8:54
  • 1
    Yes - this will work with no difficulties, but it doesn't solve the problem I was asking (although it is the simple and obvious solution)
    – chrisb2244
    Commented Oct 5, 2015 at 8:55
  • 1
    No int->double "promotion" exists. Commented Oct 5, 2015 at 9:33
  • 1
    @LightnessRacesinOrbit: It sort of does. For instance, declaring void foo(double); and then calling foo(3) is perfectly legal, as far as I can tell, and does the same thing as foo((double)3) or foo(3.0). So the issue here is more subtle. Commented Oct 5, 2015 at 19:03
  • @NateEldredge: Such an implicit conversion certainly exists, but that's not a "promotion", is it? Commented Oct 5, 2015 at 19:05

3 Answers 3

32

When you are using template argument deduction, all deductions for one template parameter must have the same result.

In your case, the two deductions for T produce double and int, which are not the same, and so deduction fails.

What you can do is only use one function argument for template argument deduction, and make the other one undeduced:

template <typename T, std::size_t A>
void foo(myClass<T, A> arg1, typename std::common_type<T>::type arg2);
//                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Note that std::common_type<T>::type is essentially just T, but because the type of arg2 is now a dependent type (its name appears to the right of a ::), it is not deduced. Therefore, only the first argument takes part in deduction and produces T = double unambiguously, and then the second function parameter just has type double, and the usual conversions take place.

As a rule of thumb, template argument deduction does not cross ::.

8
  • This is perfect, but I think you meant typename std::common_type<T>::type. Your solution also allows const and & to be added, which is great
    – chrisb2244
    Commented Oct 5, 2015 at 9:05
  • 1
    common_type decays the type; probably not relevant here, but is something to keep in mind.
    – T.C.
    Commented Oct 5, 2015 at 9:10
  • 3
    It would also be possible to add a typedef for T (i.e. value_type) inside myClass and use template <typename T1, size_t a> myClass<T1,a> operator+(myClass<T1,a> lhs, typename myClass<T1,a>::value_type const & rhs); Commented Oct 5, 2015 at 9:15
  • 1
    @LightnessRacesinOrbit: True. We probably should have a std::nondeduced<T>::type, not because it's better than std::common_type<T>::type, but because it makes the intent obvious.
    – MSalters
    Commented Oct 5, 2015 at 11:52
  • 3
    @MSalters: std::kill_deduceacity_t<T> :-)
    – Kerrek SB
    Commented Oct 5, 2015 at 12:05
17

The compiler on overload resolution fails to find a right candidate for operator+ because T is already being deducted to double and literal 5 is an integer. Solution:

template <typename T1, typename T2, size_t a>
myClass<T1,a> operator+(myClass<T1,a> lhs, const T2& rhs) {
    return lhs += T1(rhs);
}
3
  • In the case where T1 and T2 are equal, this will presumably result in an unnecessary copy - will taking the parameter by value allow a move (and would it be likely to be any faster in the case of int and double?)
    – chrisb2244
    Commented Oct 5, 2015 at 9:01
  • 4
    @chrisb2244 you could partially specialise for the case T1 == T2 Commented Oct 5, 2015 at 9:25
  • Constructor-style casts are dangerous, if you want to only allow implicit conversions, then initialize a local of type T1.
    – Ben Voigt
    Commented Oct 5, 2015 at 21:22
9

You are running into problems with template type deduction.

Both arguments are given "equal standing" when deducing the value of T, and in this case the two arguments disagree -- one says T should be int, the other says T should be double.

The right way to fix this is with Koenig operators.

Make += and + and the like friends of your class and implement inline:

template<class T, size_t a>
class myClass {
  // etc
public:
  friend myClass operator+(myClass lhs, const T& rhs) {
    lhs += rhs;
    return std::move(lhs);
  }
  friend myClass& operator+=(myClass& lhs, const T& rhs) {
    // do addition, depends on `a`
    return *this;
  }
};

this technique does something strange. It creates non-template operators based on the template type of the class. These are then found via ADL (Koenig lookup) when you invoke + or +=.

You get one of these operators per template instantiation, but they aren't template operators, so const T& isn't deduced, and conversion happens as expected.

Not the answer you're looking for? Browse other questions tagged or ask your own question.