Uses of a C++ Arithmetic Promotion Header

For this, what you can use is the ?: operator. It will give you the common type between two types. First, if the two types are the same, you are fine. Then, if the types differ, you invoke the ?: and see what type you get back.

You need to special case the non-promoted types char, short and their unsigned/signed versions thereof since applied to two of such operands of differing types, the result will be neither of them. You need also take care of the case where two classes can be converted to promoted arithmetic types. To get these right, we check whether the result of ?: is a promoted arithmetic type (in the spirit of clause 13.6), and use that type then.

// typedef eiher to A or B, depending on what integer is passed
template<int, typename A, typename B>
struct cond;

#define CCASE(N, typed) \
  template<typename A, typename B> \
  struct cond<N, A, B> { \
    typedef typed type; \
  }

CCASE(1, A); CCASE(2, B);
CCASE(3, int); CCASE(4, unsigned int);
CCASE(5, long); CCASE(6, unsigned long);
CCASE(7, float); CCASE(8, double);
CCASE(9, long double);

#undef CCASE

// for a better syntax...
template<typename T> struct identity { typedef T type; };

// different type => figure out common type
template<typename A, typename B>
struct promote {
private:
  static A a;
  static B b;

  // in case A or B is a promoted arithmetic type, the template
  // will make it less preferred than the nontemplates below
  template<typename T>
  static identity<char[1]>::type &check(A, T);
  template<typename T>
  static identity<char[2]>::type &check(B, T);

  // "promoted arithmetic types"
  static identity<char[3]>::type &check(int, int);
  static identity<char[4]>::type &check(unsigned int, int);
  static identity<char[5]>::type &check(long, int);
  static identity<char[6]>::type &check(unsigned long, int);
  static identity<char[7]>::type &check(float, int);
  static identity<char[8]>::type &check(double, int);
  static identity<char[9]>::type &check(long double, int);

public:
  typedef typename cond<sizeof check(0 ? a : b, 0), A, B>::type
    type;
};

// same type => finished
template<typename A>
struct promote<A, A> {
  typedef A type;
};

If your Complex<T> types can be converted into each other, ?: won’t find a common type. You could specialize promote to tell it how to figure out a common type of two Complex<T>:

template<typename T, typename U>
struct promote<Complex<T>, Complex<U>> {
  typedef Complex<typename promote<T, U>::type> type;
};

Usage is simple:

int main() {
  promote<char, short>::type a;
  int *p0 = &a;

  promote<float, double>::type b;
  double *p1 = &b;

  promote<char*, string>::type c;
  string *p2 = &c;
}

Note that for real-world uses, you should best catch a few cases I left out for simplicity, for example <const int, int> should be handled similar to <T, T> (you best first strip const and volatile and convert T[N] to T* and T& to T and afterwards delegate to the actual promote template – i.e do boost::remove_cv<boost::decay<T>>::type for both A and B before delegating them). If you don’t do this, the call to check will end up in an ambiguity for these cases.

Leave a Comment