15

I have a template class with an overloaded + operator. This is working fine when I am adding two ints or two doubles. How do I get it to add and int and a double and return the double?

template <class T>
class TemplateTest
{
private:
  T x;

public:

  TemplateTest<T> operator+(const TemplateTest<T>& t1)const
  {
    return TemplateTest<T>(x + t1.x);
  }
}

in my main function i have

void main()
{
  TemplateTest intTt1 = TemplateTest<int>(2);
  TemplateTest intTt2 = TemplateTest<int>(4);
  TemplateTest doubleTt1 = TemplateTest<double>(2.1d);
  TemplateTest doubleTt2 = TemplateTest<double>(2.5d);

  std::cout <<  intTt1 + intTt2 << /n;
  std::cout <<  doubleTt1 + doubleTt2 << /n;
}

I want to be able to also do this

std::cout <<  doubleTt1 + intTt2 << /n;
SoapBox
  • 20,457
  • 3
  • 51
  • 87
Sara
  • 151
  • 1
  • 3

9 Answers9

13

Here be dragons. You're getting into parts of c++ that will probably result in a lot of questions posted to StackOverflow :) Think long and hard about if you really want to do this.

Start with the easy part, you want to allow operator+ to add types that are not always the same as T. Start with this:

template <typename T2>
TemplateTest<T> operator+(const TemplateTest<T2>& rhs) {
  return TemplateTest<T>(this->x + rhs.x);
}

Note that this is templated on T2 as well as T. When adding doubleTt1 + intTt2, T will be doubleTt1 and T2 will be intTt2.

But here's the big problem with this whole approach.

Now, when you add a double and an int, what do you expect? 4 + 2.3 = 6.3? or 4 + 2.3 = 6? Who would expect 6? Your users should, because you're casting the double back to an int, thus losing the fractional part. Sometimes. Depending on which operand is first. If the user wrote 2.3 + 4, they would get (as expected?) 6.3. Confusing libraries make for sad users. How best to deal with that? I don't know.

peterh
  • 11,875
  • 18
  • 85
  • 108
Stephen
  • 47,994
  • 7
  • 61
  • 70
13

Stephen has already given a good explanation of the problems you may encounter with this. You can define overloads for all the possible combinations of all the instantiations of the template (so, you'd effectively have operators defined for double + double, int + double, double + int, etc.). This can get unwieldy fast and can be difficult to keep track of which combinations are supported.

You might be better off using a non-member function named something like Add(). The advantage of doing this is that you can specify the return type. The disadvantage is that you have to specify the return type. :-) In general, though, this is better than performing unexpected conversions automatically.

template <typename R, typename T, typename U>
TemplateTest<R> Add(const TemplateTest<T>& t, const TemplateTest<U>& u)
{
    return TemplateTest<R>(t.x + u.x);
}

invoked as:

std::cout << Add<double>(intTt1, doubleTt1) << std::endl;

C++0x will add support for a number of language features that will make this much simpler and will allow you to write a reasonable operator+ overload:

template <typename T, typename U>
auto operator+(const TemplateTest<T>& t, const TemplateTest<U>& u) 
    -> TemplateTest<decltype(t.x + u.x)>
{
    return TemplateTest<decltype(t.x + u.x)>(t.x + u.x);
}

This will ensure that the usual arithmetic conversions (integer promotion, conversion to floating point, etc.) are performed and you end up with the expected result type.

Your C++ implementation may support these C++0x features already; you'd want to consult the documentation of whatever compiler you are using.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
5

I want to be able to also do this

std::cout << doubleTt1 + intTt2 << "\n";

What you'll probably need for this case are type traits. Basically, those are template classes containing typedefs. You then partially specialize such a template to override the typedefs.

Basic example:

(This is probably a bit naïve, but it should get the basic idea across.)

template <typename A, typename B>
struct add_traits
{
    typedef A first_summand_t;   // <-- (kind of an "identity type")
    typedef B second_summand_t;  // <-- (ditto; both aren't strictly necessary)
    typedef B sum_t;             // <-- this is the interesting one!
};

Now you partially specialize that thing for various combinations of A and B:

template<>
struct add_traits<int, int>
{
    typedef int first_summand_t;
    typedef int second_summand_t;
    typedef int sum_t;             // <-- overrides the general typedef
};

template<>
struct add_traits<int, double>
{
    typedef int first_summand_t;
    typedef double second_summand_t;
    typedef double sum_t;    // <-- overrides the general typedef
};

template<>
struct add_traits<double, int>
{
    typedef double first_summand_t;
    typedef int second_summand_t;
    typedef double sum_t;    // <-- overrides the general typedef
};

Now you could write a fairly generic add operation that went like this:

template <typename A, typename B>
typename add_traits<A,B>::sum_t add(A first_summand, B second_summand)
{
    // ...
}

As you can see, you don't specify a concrete return type; instead, you let the compiler figure it out through the add_traits template class. Once the compiler generates the code for a particular add function, it will look up the types in the corresponding add_traits class, and thanks to the partially specialized versions that you provided, you can make sure that certain type "combinations" will be applied.


P.S.: The same technique would e.g. also be useful when you want to subtract unsigned numbers. One unsigned int subtracted from another can result in a negative answer; the result type would have to be a (signed) int.


P.P.S.: Corrections made according to the comments below.

stakx - no longer contributing
  • 83,039
  • 20
  • 168
  • 268
  • 1
    This is a good and reasonable answer, one I was going to write if I didn't find it, but you've made several mistakes. First "typename" should be "typedef" in your specializations. Second, since you have them in the default, you should really also include your find and second operand definitions in your specializations. Finally, you fail to use typename in the return type for the add function. BTW, your specializations are not "partial". – Edward Strange Jun 13 '10 at 03:58
  • 1
    I don't have enough rep to edit this, but here's a working example of above: http://ideone.com/x1KrO – Cristián Romo Jun 13 '10 at 05:19
  • @Noah Robers and @Cristiàn Romo: Thanks a lot for pointing out my various mistakes! That's what happens when you post in the wee hours of the day... (I edited my answer accordingly.) – stakx - no longer contributing Jun 13 '10 at 09:38
3

The add operator should generally be a free function to avoid preferring any operand type as @Stephen nicely explains. This way it's completely symmetric. Assuming you have a function get that returns the stored value, this can be like the following (alternatively, you can declare it as a friend if such a get function does not exist)

template<typename T1, typename T2>
TemplateTest<???> operator+(const TemplateTest<T1>& t1, const TemplateTest<T2>& t2)
{
  return TemplateTest<???>(t1.get() + t2.get());
}

The problem now is to find a result type. As other answers show this is possible with decltype in C++0x. You can also simulate this by using the rules of the ?: operator which are mostly quite intuitive. promote<> is a template that uses that technique

template<typename T1, typename T2>
TemplateTest< typename promote<T1, T2>::type > 
operator+(const TemplateTest<T1>& t1, const TemplateTest<T2>& t2)
{
  return TemplateTest< typename promote<T1, T2>::type >(t1.get() + t2.get());
}

Now for example if you add double and int, it will yield double as the result. Alternatively as shown in the promote<> answer, you can also specialize promote so you can apply it directly to TemplateTest types.

Community
  • 1
  • 1
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
2

If this is mainly for basic types, you could help yourself with a metafunction until the new standard rolls in. Something along the lines of

template<typename T1,
         typename T2,
         bool T1_is_int = std::numeric_limits<T1>::is_integer,
         bool T2_is_int = std::numeric_limits<T2>::is_integer,
         bool T1_is_wider_than_T2 = (sizeof(T1) > sizeof(T2)) > struct map_type;

template<typename T1, typename T2, bool b> struct map_type<T1, T2, b, b, true > { typedef T1 type; };
template<typename T1, typename T2, bool b> struct map_type<T1, T2, b, b, false> { typedef T2 type; };
template<typename T1, typename T2, bool b> struct map_type<T1, T2, false, true , b> { typedef T1 type; };
template<typename T1, typename T2, bool b> struct map_type<T1, T2, true , false, b> { typedef T2 type; };

template<typename T, typename U>
typename map_type<TemplateTestT<T>, TemplateTest<U> >::type
operator+(TemplateTest<T> const &t, TemplateTest<U> const &u) {
  return typename map_type<TemplateTest<T>, TemplateTest<U> >::type(x + t1.x);
}

Of course, this is best combined with the char_traits idea:

template <typename A, typename B>
struct add_traits
{
  typedef A first_summand_t;
  typedef B second_summand_t;
  typedef typename map_type<A, B>::type sum_t;
};

So that you can still specialise for types that don't have a numeric_limits overload.

Oh, and in production code, you'll probably want to properly namespace that and add something for signed/unsigned mismatches in integer types.

2

Get a compiler that supports the new C++0x decltype operator.

template < typename T1, typename T2 >
auto add(T1 t1, T2 t2) -> decltype(t1+t2)
{
  return t1 + t2;
}

Now you don't have to fart around with those "traits" classes.

Edward Strange
  • 40,307
  • 7
  • 73
  • 125
1

You can add int and double values by using templates.In the function, specify 2 arguments and while passing values to the functions specify its types in angular brackets.

example:

//template
template<typename T1, typename T2>
void add(T1 a, T2 b)
{
    //for adding values
    cout<<"\n\nSum : "<<a+b;
}

int main ()
{
    //specify types while passing values to funcion
    add<int,double>(4,5.5454);
    add<float,int>(4.7,5);
    add<string,string>("hi","bye");
    return 0;
}
Amruta Ghodke
  • 115
  • 1
  • 3
1

Newer answer to an old question. For C++0x you can go with decltype as other answers have talked about. I would argue that common_type is more made for the situation than decltype.

Here is an example of common_type used in a generic add:

#include <iostream>
#include <array>
#include <type_traits>

using namespace std;

template <typename T, typename U, unsigned long N>
 array<typename common_type<T, U>::type, N> // <- Gets the arithmetic promotion
 add_arrays(array<T, N> u, array<U, N> v)
{
    array<typename common_type<T, U>::type, N> result; // <- Used again here
    for (unsigned long i = 0; i != N; ++i)
    {
        result[i] = u[i] + v[i];
    }
    return result;
}

int main()
{
    auto result = add_arrays( array<int,    4> {1, 2, 3, 4},
                              array<double, 4> {1.0, 4.23, 8.99, 55.31} );

    for (size_t i = 0; i != 4; ++i)
    {
        cout << result[i] << endl;
    }

    return 0;
}

it basically returns the value that different arithmetic operations would promote to. One nice thing about it is that it can take any number of template args. Note: don't forget to add the ::type at the end of it. That is what gets the actual type result that you want.

For those working pre-c++11 still, there is a boost version of common_type as well

Cory
  • 151
  • 12
0

This is technically possible by defining an implicit case to TemplateTest<double>:

operator TemplateTest<double>() {
    return TemplateTest<double>((double)x);
}

Practically this probably isn't a great idea though, as x can't necessarily be safely cast to a double; it happens that you're using a TemplateTest<int> here, but you could be using a TemplateTest<std::string> later. You should probably rethink what you're doing and decide if you're sure you actually need this behavior

Michael Mrozek
  • 169,610
  • 28
  • 168
  • 175