Uses of a C++ Arithmetic Promotion Header

后端 未结 2 1393
没有蜡笔的小新
没有蜡笔的小新 2020-11-27 19:00

I\'ve been playing around with a set of templates for determining the correct promotion type given two primitive types in C++. The idea is that if you define a custom numer

相关标签:
2条回答
  • 2020-11-27 19:47

    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.

    0 讨论(0)
  • 2020-11-27 19:56

    This is definitely useful -- we use these sorts of things in the math library that I work on for correctly typing intermediate values in expressions. For example, you might have a templated addition operator:

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

    This way, you can write a generic operator that will handle different argument types, and it will return a value of the appropriate type to avoid precision loss in the expression that it appears in. It's also useful (in things like vector sums) for properly declaring internal variables within these operators.

    As for the question of what ought to go with these: I just checked in our source code where we define them, and all we have there are just the simple ArithmeticPromotion declaration you describe -- three generic versions to resolve the complex-complex, complex-real, and real-complex variants using the specific real-real ones, and then a list of real-real ones -- about 50 lines of code in all. We don't have any other helper templates with them, and it doesn't (from our usage) look like there are any natural ones that we'd use.

    (FWIW, if you don't want to write this yourself, download our source from http://www.codesourcery.com/vsiplplusplus/2.2/download.html, and pull out src/vsip/core/promote.hpp. That's even in the part of our library that's BSD-licensed, though it doesn't actually say so in the file itself.)

    0 讨论(0)
提交回复
热议问题