C ++算术推广标题的使用

时间:2010-03-11 15:41:24

标签: c++ math templates boost

我一直在玩一套模板,用于在C ++中给出两种原始类型来确定正确的促销类型。这个想法是,如果你定义一个自定义数字模板,你可以使用它们来确定,例如,基于传递给模板的类的操作符+函数的返回类型。例如:

// 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在其数学/工具/促销标题中有类似的东西,但它更多的是让值准备好传递给标准C数学函数(期望2个整数或2个双精度)并绕过所有的整体类型。是否更容易完全控制对象的转换方式?

TL; DR:您希望在推广本身的机制之外的算术推广标题中找到哪种辅助模板?

2 个答案:

答案 0 :(得分:12)

为此,您可以使用的是?:运算符。它将为您提供两种类型之间的通用类型。首先,如果两种类型相同,你就可以了。然后,如果类型不同,则调用?:并查看您返回的类型。

您需要特殊情况下非促销类型charshort及其未签名/签名版本,因为它们应用于两个不同类型的操作数,结果将不是它们。您还需要注意两个类可以转换为提升的算术类型的情况。为了使这些正确,我们检查?:的结果是否是提升的算术类型(在条款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模板 - 即{{1}在委派之前,{} {}和boost::remove_cv<boost::decay<T>>::type都可以使用}。如果您不这样做,对A的调用将最终导致这些情况模糊不清。

答案 1 :(得分:2)

这绝对是有用的 - 我们在数学库中使用这些类型的东西来处理正确的表达式中的中间值。例如,您可能有一个模板化的加法运算符:

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许可的部分,尽管它实际上并没有在文件本身中这么说。)