1

I have the following class

template<typename hi_t, typename lo_t>
struct int_t
{
hi_t hi;
lo_t lo;

int_t() : lo(0), hi(0) {}
int_t(int value) : lo(value), hi( value<0u? -1: 0 ) {}
int_t(unsigned value) : lo(value), hi( 0 ) {}

int_t& operator+=(const int_t& rhs)
{
    lo_t _lo = lo;

    lo += rhs.lo;
    hi += rhs.hi;
    hi += (int)(lo < _lo);

    return *this;
}

template<typename hi_t, typename lo_t>
inline friend int_t<hi_t, lo_t> operator+(const int_t<hi_t, lo_t>&, const int_t<hi_t, lo_t>&);
};

template<typename hi_t, typename lo_t>
int_t<hi_t, lo_t> operator+(const int_t<hi_t, lo_t>& lhs, const int_t<hi_t, lo_t>& rhs)
{ return int_t<hi_t, lo_t>(lhs) += rhs; }

when executing the following code

typedef int_t<long long, unsigned long long> int128;

int main()
{
    int128 i = 1024;
    i = i + 20;
}

the compiler produce the error:

'int_t<hi_t,lo_t> operator +(const int_t<hi_t,lo_t> &,const int_t<hi_t,lo_t> &)' : could not deduce template argument for 'const int_t<hi_t,lo_t> &' from 'int'

when i put the code of template operator inside the class body - with removing the template line from the friend operator - it works, but with friend operator outside the class it can't deduce the operator. i thought when compiler generates code for this template operator the input parameters and return value will be of type int128 so it should have no problem from casting from int to that type.

UPDATE

if we define the friend operator inside the class as following the previous example works

template<typename hi_t, typename lo_t>
struct int_t
{
hi_t hi;
lo_t lo;

int_t() : lo(0), hi(0) {}
int_t(int value) : lo(value), hi( value<0u? -1: 0 ) {}
int_t(unsigned value) : lo(value), hi( 0 ) {}

int_t& operator+=(const int_t& rhs)
{
    lo_t _lo = lo;

    lo += rhs.lo;
    hi += rhs.hi;
    hi += (int)(lo < _lo);

    return *this;
}

friend int_t operator+(const int_t& lhs, const int_t& rhs)
{ return int_t(lhs) += rhs; }

};

the problem happens when trying to define the template operator outside the class

Muhammad
  • 1,598
  • 3
  • 19
  • 31
  • Doesn't compile under G++ either, but the error is different; it complains that the template parameters of the friend function shadows the template parameters of the class. It can't implicitly convert 20 to int_t either. Also, value < 0u is always false! I'll try rewriting it in my style, perhaps I can catch the underlying error. – Frigo Aug 08 '11 at 13:15
  • @Frigo : the friend declaration is a bit off (ref. [C++ FAQ Lite 35.16](http://www.parashift.com/c++-faq-lite/templates.html#faq-35.16) eg.), but I assume Muhammad's compiler is ok with that. – Sander De Dycker Aug 08 '11 at 13:25
  • I'm using VC++ 2008 compiler with language extension off and level 4 warning and the only error it produce is what i mentioned. – Muhammad Aug 08 '11 at 13:30

4 Answers4

4

The code is trickier than it seems at first look. The most tricky part is your declaration of a friend function. You should take a look at this answer regarding befriending a function from a template. The short recommendation is that you remove the templated operator+ and you implement it as a non-template friend function inside the class declaration:

template<typename hi_t, typename lo_t>
struct int_t
{
// ...
    friend int_t operator+(int_t lhs, const int_t& rhs ) {
        return lhs+=rhs;
    }
};

As of the particular error, it might not be that helpful, and it might even be confusing, but you can start by taking into account that a template will only be taken into account for operator overloading if after type deduction it is a perfect match (i.e. no conversion required). That means that int128_t + int will never be matched by a templated operator+ that has the same type for both left and right hand side, even if there is a conversion.

The proposed solution above declares (and defines) a non-template function. Because it is defined inside the class, it will only be considered by Argument Dependent Lookup, and will thus only apply when one of the operators is a int_t, if it is found by ADL, then it will be picked up for overload resolution with the usual non-template rules, which means that the compiler is able to use any possible conversions to both the lhs or rhs (one of them must be an int_t instantiation if it was found by ADL, but it will convert the other).

Community
  • 1
  • 1
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • Yup, correct explanation, it simply won't match int to the infinite number of addition operators. He should simply ditch the template declaration altogether. friend int_t operator + (const int_t& x, const int& y){...} – Frigo Aug 08 '11 at 13:36
  • @Frigo: the issue goes beyond the deduction, consider `std::min` when an `int` and a `long int` are passed (or any other convertible but different types), there is no deduction problem, there aren't infinite possibilities, and yet the compiler cannot use that template because the types are not a perfect match after deduction – David Rodríguez - dribeas Aug 08 '11 at 13:55
  • 1
    @David: It can be made more human readable to skip the template argument lists when inside int_t, like `friend int_t operator+ ...`. – Sebastian Mach Aug 08 '11 at 14:29
  • @phresnel: Right I was not thinking on readability :) I have edited the answer and removed the template arguments that are optional in this case. – David Rodríguez - dribeas Aug 08 '11 at 16:16
1

Turn on all your compiler warnings.

You are using the same template parameter names in your friend declarations as in the template class itself, which is not good; rename them. Here's one solution: Remove the out-of-line operator definition and make the inline definition this:

template<typename H, typename L>
inline friend int_t operator+(const int_t & lhs, const int_t<H, L> & rhs)
{
  return int_t(lhs) += rhs;
}

Now, since your RHS is an arbitrary type, you have to mention the type:

i = i + int128(20);

This is because there is no way to deduce parameters H,L from the integer 20 so that an appropriate conversion to int_t<H,L>(20) could be performed (see Nawaz's answer)!


To take advantage of your conversion constructor from int, you can only operate on the same type, not a templated other type. To this end, add a non-template operator:

int_t operator+(const int_t & rhs) const { return int_t(*this) += rhs; }

Now you can say i = i + 20;, using the int_t(int) constructor.

Update: As the OP suggests, to allow for symmetric invocation (i = 50 + i;), and in order to only allow operations within a fixed type as David suggests, we should remove both the unary operator and the templated friend binary operator and instead just have a non-templated binary friend:

friend int_t operator+(const int_t & lhs, const int_t & rhs) { return int_t(lhs) += rhs; }

That's a matter of design choice; I would personally favour the final version.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • While this is a solution, it is not the best solution, as you are blindly befriending *all* instantiations of the template (including potential specializations). – David Rodríguez - dribeas Aug 08 '11 at 13:15
  • @David: Sure, that's a design decision for the OP to make. Me, I'd probably just stick to operations within one type. If you want to get fancy, you could add type traits to check if both constituent types are convertible. – Kerrek SB Aug 08 '11 at 13:17
  • replace the firend operator with next code and try the example again: friend int_t operator+(const int_t& lhs, const int_t& rhs) { return int_t(lhs) += rhs; } – Muhammad Aug 08 '11 at 13:28
  • @Muhammad: That's also fine, if you also remove the unary operator. I updated my answer. – Kerrek SB Aug 08 '11 at 13:35
0

Are you certain that operator+= should be a member template? Usually, you just

inline friend int_t operator+(const int_t&, const int_t&) {...}
Sebastian Mach
  • 38,570
  • 8
  • 95
  • 130
-1

I'am not sure but maybe compiler needs template argument again

...
...
...

template<typename hi_t, typename lo_t>
int_t& operator+=(const int_t& rhs)
{
...
...
...
Nelstaar
  • 769
  • 9
  • 26