7

I'm curious if there's any way to do this in C++. Let's say I have a templated vector class:

template <typename T>
class vector {          
public:
      vector(T a, T b, T c) : x(a), y(b), z(c) {}

      T x,y,z;
};

And then I have a templated addition operator:

template <typename A, typename B> 
vector<A> operator +(const vector<A> &a, const vector<B> &b) { 
   return vector<A>(a.x+b.x, a.y+b.y, a.z+b.z); 
}

I'm curious if it's possible to modify that operator so the result is whichever of the two types A and B is more precise, aside from manually specializing it.

For example:

vector<float>       + vector<double> would produce a vector<double>, 
vector<long double> + vector<float>  would produce a vector<long double>

My guess would be that there's no automatic support for this in C++, but I thought I'd ask.

gct
  • 14,100
  • 15
  • 68
  • 107

9 Answers9

9

There isn't any built-in support in the form of a library, but you can accomplish this using the conditional (?:) operator.

In reply to another answer, Johannes Schaub posted a promote<T, U> template that wraps the logic up quite nicely. With the template, you should be able to write:

template <typename A, typename B>  
vector< typename promote<A, B>::type >
operator+(const vector<A> &a, const vector<B> &b) 
{     
    return vector< typename promote<A, B>::type >(a.x+b.x, a.y+b.y, a.z+b.z);  
} 
Community
  • 1
  • 1
James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • Thanks for the link, I had missed that pearl from Johannes (this guy is crazy :p) – Matthieu M. Feb 01 '11 at 19:31
  • 2
    People upvoting this answer: why are you upvoting this but not upvoting Johannes's answer? Are you even clicking the link to read it? He has done all the hard work; why upvote my answer if you aren't going to upvote the answer that gives the solution to the problem? – James McNellis Feb 01 '11 at 22:33
  • 1
    Sometimes I feel like C++ metaprogramming is about misusing the language constructs as much as possible... – Roman L Feb 02 '11 at 02:43
7

in C++0x, you could say:

template <typename A, typename B> 
auto operator +(const vector<A> &a, const vector<B> &b) -> vector<decltype(a.x + b.x)>
{
    //...
}

In C++03, you need to define all the combinations yourself, although you can do it in a reusable op_traits scheme that can be applied to a variety of different operators. James McNellis provides some details on this in his answer

Community
  • 1
  • 1
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • @wilhelmtell: This is the easiest solution. C++03 does not offer anything even remotely as easy as this. – Puppy Feb 01 '11 at 21:09
3

Andrei Alexandrescu discussed this in his 1st April 2001 DDJ article Generic: Min and Max Redivivus.

In short, the general problem is very complex.

Andrei used 80 lines of support code, those lines in turn relying on the Loki library.

Cheers & hth,.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
1

There is a relatively easy way to do this with template specializations

 template< typename A >
 struct TypePrecision {
    static const int precisionLevel;
 };

 template< typename A >
 const int TypePrecision< A >::precisionLevel = 0;


 template<>
 struct TypePrecision< float > {
    static const int precisionLevel;
 };
template<>
 struct TypePrecision< long float > {
    static const int precisionLevel;
 };
  template<>
 struct TypePrecision< double > {
    static const int precisionLevel;
 };
template<>
 struct TypePrecision< long double > {
    static const int precisionLevel;
 };

 template<>
 const int TypePrecision< float >::precisionLevel = 1;
 template<>
 const int TypePrecision< long float >::precisionLevel = 2;
 template<>
 const int TypePrecision< double >::precisionLevel = 3;
 template<>
 const int TypePrecision< long double >::precisionLevel = 4;

Then you use this to create a HigherPrecisionType

 template < typename A , typename B >
 struct HigherPrecisionType
 { 
     static const int APrecision;
     static const int BPrecision;
 };

template < typename A , typename B >
const int HigherPrecisionType< A, B >::APrecision= TypePrecision< A >::precisionLevel;

template < typename A , typename B >
const int HigherPrecisionType< A, B >::BPrecision= TypePrecision< B >::precisionLevel;

I'm not sure how to compare these to get a typedef in the specialization to the appropiate type though. But i hope you get the idea

lurscher
  • 25,930
  • 29
  • 122
  • 185
  • This approach works and I've used something similar before. You need some more template glue that takes two types and compares their precision level in order to do a "conditional" typedef, but it's very feasible. – Flexo Feb 01 '11 at 19:01
  • The trick is to do a subtraction and then a partial specialisation. – Flexo Feb 01 '11 at 19:02
  • hmm the thing is that i know how to do partial specialisation when the substraction is zero, however my question is how to do a partial specialisation on a int template parameter when the parameter is a) negative b) positive? – lurscher Feb 01 '11 at 19:13
  • 2
    you can use ?: operator to make it so that negative is (e.g.) 0. Then in the 0 you take the left and for everything else you take the right. (or vice versa as appropriate) – Flexo Feb 01 '11 at 19:28
  • right! i wasn't aware that the ?: operator allows us to make decisions at build time, great! i thought it was just syntactic sugar for if – lurscher Feb 01 '11 at 19:35
1

Pattern "Type Selection" (read about it in "Modern C++ Design") can be useful here.

template <bool flag, typename T, typename U>
struct Select {
    typedef T Result;
};

template <typename T, typename U>
struct Select<false, T, U> {
    typedef U Result;
};

...

template <typename A, typename B> 
vector<Select<sizeof(A) > sizeof(B), A, B>::Result> operator +(const vector<A> &a, const vector<B> &b) { 
   return vector<Select<sizeof(A) > sizeof(B), A, B>::Result>(a.x+b.x, a.y+b.y, a.z+b.z); 
}
Vladimir Lagunov
  • 1,895
  • 15
  • 15
  • This wouldn't compile. `Result` is a dependent name, so `typename` is required! – Nawaz Feb 01 '11 at 19:15
  • This is too simplistic... `sizeof (float) < sizeof (long long)`, but `float` has greater range than `long long`. – Ben Voigt Feb 03 '11 at 03:46
1

I'm choosing the type greater in size:

Helper templates:

template<bool b, typename A, typename B>
struct choose_if
{
    typedef A type;
};    
template<typename A, typename B>
struct choose_if<false, A, B>
{
    typedef B type;
};
template<typename A, typename B>
struct greater 
{
    static const bool value = sizeof(A) > sizeof(B);
    typedef vector<typename choose_if<value, A, B>::type> type;
};

Now use it:

template <typename A, typename B> 
typename greater<A, B>::type operator +(const vector<A> &a, const vector<B> &b) 
{ 
    typedef typename greater<A, B>::type  type;
    return type(a.x+b.x, a.y+b.y, a.z+b.z); 
}

See online demonstration : http://www.ideone.com/PGyA8

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • Unfortunately `sizeof` is not a good indicator of which type has a larger range. e.g. `sizeof (float) < sizeof (long long)`. – Ben Voigt Feb 03 '11 at 03:44
  • @Ben Voigt: Yeah. I know this is not a correct answer as such... rather how one could start and proceed. Actually I wanted to improve this... but not getting time to do that.. – Nawaz Feb 03 '11 at 03:47
0

You can accomplish your goal somewhat by using function overloading. Meaning that in addition to the generic:

template <typename A, typename B> 
vector<A> operator +(const vector<A> &a, const vector<B> &b) { 
   return vector<A>(a.x+b.x, a.y+b.y, a.z+b.z); 
}

you also declare overloads for specific types, and these then get used rather than generically manufactured ones:

vector<double> operator +(const vector<float> &a, const vector<double> &b) { 
   return vector<double>(a.x+b.x, a.y+b.y, a.z+b.z); 
}

Your other option would be to implement conversion operators on your vector template for the types required. Have a float vector be able to return a double vector via an operator.

0

Yep. Here's the C++03 method:

template < typename T1, typename T2 >
struct which_return;

template < typename T >
struct which_return<T,T> { typedef std::vector<T> type; };

template <  >
struct which_return<int,double> { typedef std::vector<double> type; };

template < >
struct which_return<double,int> : which_return<int,double> {};

// etc...
template < typename T1, typename T2 >
typename which_return<T1,T2>::type operator+ (std::vector<T1> const&, std::vector<T2> const&)
{
  // ...
}

Obviously you do it the C++0x way if you can.

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

You'll never be able to accomplish this:

vector<float> + vector<double> would produce a vector<double>

without massive trickery or returning a pointer to some gizmo of your own design because operator+ must return a type that is known at compile-time. You are asking to return a type that is determined at run-time.

John Dibling
  • 99,718
  • 31
  • 186
  • 324
  • 4
    templates are specialized at compile-time, there's no reason whatsoever that the compiler cannot create an overload accepting a `vector` and a `vector` and return a `vector`. The trick is getting it to do this without duplication of source code for each combination. – Ben Voigt Feb 01 '11 at 18:56