Использование C ++ арифметического заголовка продвижения - PullRequest
7 голосов
/ 11 марта 2010

Я поигрался с набором шаблонов для определения правильного типа продвижения по двум примитивным типам в C ++. Идея состоит в том, что если вы определяете пользовательский числовой шаблон, вы можете использовать их для определения типа возврата, скажем, функции operator + на основе класса, переданного шаблонам. Например:

// Custom numeric class
template <class T>
struct Complex {
    Complex(T real, T imag) : r(real), i(imag) {}
    T r, i;
// Other implementation stuff
};

// Generic arithmetic promotion template
template <class T, class U>
struct ArithmeticPromotion {
    typedef typename X type;  // I realize this is incorrect, but the point is it would
                              // figure out what X would be via trait testing, etc
};

// Specialization of arithmetic promotion template
template <>
class ArithmeticPromotion<long long, unsigned long> {
    typedef typename unsigned long long type;
}

// Arithmetic promotion template actually being used
template <class T, class U>
Complex<typename ArithmeticPromotion<T, U>::type>
operator+ (Complex<T>& lhs, Complex<U>& rhs) {
    return Complex<typename ArithmeticPromotion<T, U>::type>(lhs.r + rhs.r, lhs.i + rhs.i);
}

Если вы используете эти шаблоны продвижения, вы можете более или менее рассматривать ваши пользовательские типы, как если бы они были примитивами с теми же правилами продвижения, которые применяются к ним. Итак, я предполагаю, что у меня есть вопрос: может ли это быть чем-то полезным? И если да, то какие общие задачи вы бы хотели использовать для простоты использования? Я работаю в предположении, что одних только шаблонов продвижения недостаточно для практического применения.

Между прочим, Boost имеет нечто похожее в заголовке math / tools / promotion, но это действительно больше для того, чтобы получить значения, готовые для передачи стандартным математическим функциям C (которые ожидают либо 2 ints, либо 2 double), и обходит все интегральные типы. Является ли что-то настолько простым, предпочтительнее, чем иметь полный контроль над тем, как конвертируются ваши объекты?

TL; DR: Какие типы вспомогательных шаблонов вы ожидаете найти в арифметическом заголовке рекламного объявления, помимо механизма, который выполняет саму рекламную кампанию?

Ответы [ 2 ]

12 голосов
/ 15 марта 2010

Для этого вы можете использовать оператор ?:. Это даст вам общий тип между двумя типами. Во-первых, если два типа одинаковы, у вас все хорошо. Затем, если типы различаются, вы вызываете ?: и смотрите, какой тип вы получите.

Вам необходимо в особом случае не продвигать типы char, short и их неподписанные / подписанные версии, поскольку применительно к двум таким операндам различных типов результат не будет ни одним из них. Вам также необходимо позаботиться о случае, когда два класса могут быть преобразованы в повышенные арифметические типы. Чтобы понять это правильно, мы проверяем, является ли результат ?: повышенным арифметическим типом (в духе пункта 13.6), и затем используем этот тип.

// 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;
};

Если ваши Complex<T> типы могут быть преобразованы друг в друга, ?: не найдет общий тип. Вы могли бы специализировать promote, чтобы рассказать ему, как определить общий тип двух Complex<T>:

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

Простое использование:

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;
}

Обратите внимание, что для реальных применений лучше всего поймать несколько случаев, которые я упустил для простоты, например, <const int, int> должен обрабатываться аналогично <T, T> (вам лучше всего использовать первую полосу const и volatile и преобразуйте T[N] в T* и T& в T и затем делегируйте действительный шаблон promote - т.е. сделайте boost::remove_cv<boost::decay<T>>::type для A и B перед их делегированием). Если вы этого не сделаете, для этих случаев вызов на номер check окажется неопределенным.

2 голосов
/ 12 марта 2010

Это определенно полезно - мы используем такие вещи в математической библиотеке, над которыми я работаю, для правильного ввода промежуточных значений в выражениях. Например, у вас может быть шаблонный оператор сложения:

template<typename Atype, typename Btype>
type_promote<Atype, Btype>::type operator+(Atype A, Btype B);

Таким образом, вы можете написать универсальный оператор, который будет обрабатывать различные типы аргументов, и он будет возвращать значение соответствующего типа, чтобы избежать потери точности в выражении, в котором он появляется. Это также полезно (в таких вещах, как векторные суммы ) для правильного объявления внутренних переменных внутри этих операторов.

Что касается вопроса о том, что должно идти с ними: я только что проверил в нашем исходном коде, где мы их определяем, и все, что у нас есть, - это просто простое объявление ArithmeticPromotion, которое вы описываете - три универсальные версии для решения сложных сложные, сложно-реальные и реально-сложные варианты, использующие конкретные реально-реальные, а затем список реально-реальных - всего около 50 строк кода. У нас нет никаких других вспомогательных шаблонов, и, по нашему мнению, они не выглядят так, как будто бы мы использовали естественные.

(FWIW, если вы не хотите писать это самостоятельно, загрузите наш исходный код с http://www.codesourcery.com/vsiplplusplus/2.2/download.html, и извлеките src/vsip/core/promote.hpp. Это даже в той части нашей библиотеки, которая лицензирована BSD, хотя и не не говорите так в самом файле.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...