Welcome, guest! Please login or register.

    * Shoutbox

    RefreshHistory
    • ASDss: where do u download source and clients now
      August 18, 2017, 10:39:31 PM
    • ASDss: yo
      August 18, 2017, 10:39:20 PM
    • dan v jad: click me 4 da fun ;)[link]
      August 18, 2017, 04:39:58 PM
    • stCky: Palidinho is your OpenGL (was it OpenGL?) stuff open source anywhere?
      August 16, 2017, 09:07:22 PM
    • Travas:BUILD THE WALL
      August 15, 2017, 09:28:49 PM
    • Travas: i have ass cancer
      August 15, 2017, 09:23:29 PM
    • stCky: what are the fudge are you tryna ask?
      August 15, 2017, 08:21:35 PM
    • bader: what are the rsps community alive ?
      August 15, 2017, 05:46:16 PM
    • bader: yo guys
      August 15, 2017, 05:46:08 PM
    • Spacehost:[link] Updated our thread :)
      August 15, 2017, 09:40:34 AM
    • Adaro: The client is in Download section at Homepage
      August 15, 2017, 01:09:20 AM
    • FaTe_Of_GoDs: where do i get the client?????????????
      August 14, 2017, 05:23:14 PM
    • stCky: can anyone help me? I cant login to the shoutbox
      August 13, 2017, 05:45:15 PM
    • drubrkletern: appeal denied
      August 13, 2017, 02:35:27 PM
    • King_Trout:[link]
      August 13, 2017, 11:17:12 AM
    • Cole1497: no sorry
      August 13, 2017, 10:27:14 AM
    • ayz: yo can anyone explain something to me
      August 13, 2017, 08:08:51 AM
    • coolking12: Hi
      August 13, 2017, 04:16:06 AM
    • stCky: n+1
      August 11, 2017, 06:09:24 PM
    • PalidinoDH: How many more pages are going to show errors before this dude gets on and fixes shit
      August 11, 2017, 04:57:00 PM

    Author Topic: Introduction to C++ Metaprogramming: Dimensional Analysis  (Read 912 times)

    0 Members and 1 Guest are viewing this topic.

    Offlinejustaguy

    • Member
    • ****
    • *
    • Posts: 707
    • Thanks: +0/-0
      • View Profile
    Introduction to C++ Metaprogramming: Dimensional Analysis
    « on: October 18, 2015, 02:12:41 PM »
    Part 3 of the introduction to C++11 metaprogramming series will involve dimensional analysis, a practical application to what has been taught in the last two parts. This example of template metaprogramming comes from the C++ Template Metaprogramming book by Abrahams and Gurtovoy (a highly recommended read).

    What is dimensional analysis?

    To directly quote from Wikipedia:
    In engineering and science, dimensional analysis is the analysis of the relationships between different physical quantities by identifying their fundamental dimensions (such as length, mass, time, and electric charge) and units of measure (such as miles vs. kilometers, or pounds vs. kilograms vs. grams) and tracking these dimensions as calculations or comparisons are performed.
    One technique used to keep track of dimension is by setting a list of integers depending on if the value has a dimension associated with it. We will use the `list_c` construct designed in part 2 to keep track of the dimensions associated with a value. Why is it useful to use C++'s type system to solve this problem? Here are a few reasons:
    • It utilizes the strengths of C++'s type system which can provide flexibility for how you write your code.
    • Errors in calculations can be caught at compile-time as opposed to run-time; this also eliminates the danger of having your program continue evaluating potentially incorrect intermediate steps.
    • More efficient than a naive array implementation.
    This post will use length and time to calculate velocity as an example.

    `transform` and `equal`

    Before we continue any further, we will define two important metafunctions for dealing with lists. The first one is `transform`, which takes two lists and a metafunction as parameters, and iterates through both lists calling `fn` on the car of each list. The definition of `transform` and `transform_f` are as follows:
    Code: C++
    1. struct transform_f {
    2.   template<typename list1, typename list2, typename fn>
    3.   struct apply : cons<
    4.     typename fn::template apply<typename car<list1>::type, typename car<list2>::type>::type,
    5.     typename transform_f::template apply<
    6.       typename cdr<list1>::type,
    7.       typename cdr<list2>::type,
    8.       fn
    9.     >::type
    10.   >{};
    11.  
    12.   template<typename list1, typename fn>
    13.   struct apply<list1, empty, fn>: cons<
    14.     typename fn::template apply<typename car<list1>::type, empty>,
    15.     typename transform_f::template apply<
    16.       typename cdr<list1>::type,
    17.       empty,
    18.       fn
    19.     >::type
    20.   >{};
    21.  
    22.   template<typename list2, typename fn>
    23.   struct apply<empty, list2, fn>: cons<
    24.     typename fn::template apply<empty, typename car<list2>::type>::type,
    25.     typename transform_f::template apply<
    26.       empty,
    27.       typename cdr<list2>::type,
    28.       fn
    29.     >::type
    30.   >{};
    31.  
    32.   template<typename fn>
    33.   struct apply<empty, empty, fn>: empty {};
    34. };
    35.  
    36. template<typename list1, typename list2, typename fn>
    37. struct transform : transform_f::template apply<list1, list2, fn>{};
    A couple examples of using `transform`:
    Code: C++
    1. using l1 = list_c<int, 1, 2, 3>;
    2. using l2 = list_c<int, 3, 2, 1>;
    3. transform<l1, l2, lambda<minus>>::type// == list_c<int, -2, 0, 2>
    4. transform<l1, l2, lambda<plus>>::type// == list_c<int, 4, 4, 4>

    The second list function we will need is `equal`:
    Code: C++
    1. struct equal_f {
    2.   template<typename list1, typename list2, typename pred = lambda<std::is_same>>
    3.   struct apply : std::conditional<
    4.     !pred::template apply<
    5.       typename car<list1>::type,
    6.       typename car<list2>::type
    7.     >::type::value,
    8.     bool_<false>,
    9.     typename equal_f::template apply<
    10.       typename cdr<list1>::type,
    11.       typename cdr<list2>::type,
    12.       pred
    13.     >::type
    14.   >{};
    15.  
    16.   template<typename list, typename pred>
    17.   struct apply<empty, list, pred>: bool_<false>{};
    18.  
    19.   template<typename list, typename pred>
    20.   struct apply<list, empty, pred>: bool_<false>{};
    21.  
    22.   template<typename pred>
    23.   struct apply<empty, empty, pred>: bool_<true>{};
    24. };
    25.  
    26. template<typename list1, typename list2, typename pred = lambda<std::is_same>>
    27. struct equal : equal_f::template apply<list1, list2, pred>{};
    As the name of this metafunction might suggest, it checks if two lists are equal by comparing each element using the given `pred` predicate metafunction. By default, the predicate metafunction is C++11's `std::is_same` which checks if two types are the same.

    Now that we have defined the requisite metafunctions, we can start defining a class to maintain a magnitude and dimension.

    Notation

    We will use `list_c` to keep track of the associated properties attached to a magnitude. In this post, we will be calculating velocities from distances over time. If you know basic physics, this translates to the following equation: v = d/t, where `d` is a distance, and `t` is time. The seven base SI units are: length, mass, time, electric current, temperature, luminous intensity, and amount of substance. These are the properties we will be keeping track and calculating on.

    By utilizing the `list_c` metafunction, we can define a type which keeps track of these properties like so:
    Code: C++
    1. // type T, length, mass, time, etc...
    2. list_c<T, 1, 0, 0, ...>
    For example, we can define length, time, and velocity types like so:
    Code: C++
    1. using Length = list_c<int,0,1,0,0,0,0,0>;
    2. using Time = list_c<int,0,0,1,0,0,0,0>;
    3. using Velocity = list_c<int,0,1,-1,0,0,0,0>;
    Each index (except index 0 which designates a type to the value) is designates the dimension associated with the value. So for example, the `Time` type has a 1 at index 3, since that index designates one unit of time in our notation. Note that these lists don't contain just 1's or 0's. Velocity is calculated as distance over time, we set the length index 1 because it has a distance in the numerator, and a -1 at the time index since the time variable is in the denominator. This correlates to the fact that 1/t <=> t^-1.

    Now when we calculate addition or subtraction, we just need to make sure the dimensions are the same. We need to do this to ensure that adding a time value to a mass value is a calculation error; it wouldn't make sense to allow this, while making sure adding or subtracting time values from each other does work.

    Multiplication and division become slightly more difficult, since a distance divided a time yields a velocity, we need a way to perform operations on our type's list elements so we can transform a `list_c` for a length and a `list_c` for a time, into a `list_c` for a velocity. That is:
    Code: C++
    1. transform<distance, time, ?>::type== Velocity
    This is why we need a `transform` metafunction, because multiplication is simply transforming the distance and time dimensions by adding their indices together, and subtraction subtracts their indices. Here's an exercise for you: which transform operation produces a type that is equivalent to the `Velocity` type we defined earlier?
    Code: C++
    1. // This one?
    2. transform<list_c<int,0,1,0,0,0,0,0>, list_c<int,0,0,1,0,0,0,0>, lambda<plus>>::type== list_c<int,0,1,1,0,0,0,0>
    3. // Or this one?
    4. transform<list_c<int,0,1,0,0,0,0,0>, list_c<int,0,0,1,0,0,0,0>, lambda<minus>>::type== list_c<int,0,1,-1,0,0,0,0>
    (click to show/hide)

    Writing the code

    Now that we have the concept down, let's start digging in! Since we will be keeping track of the dimension and magnitude, we can define a class which does the job:
    Code: C++
    1. template<typename Dim, typename T>
    2. class Value {
    3.   T magnitude;
    4. public:
    5.   Value(T magnitude): magnitude(magnitude){}
    6.  
    7.   template<typename T2>
    8.   Value& operator=(const Value<Dim, T2>& rhs){
    9.     return*this;
    10.   }
    11.  
    12.   T constexpr getMagnitude()const{
    13.     return magnitude;
    14.   }
    15. };

    Now if we want to perform basic addition and subtraction on values with the same magnitude, we can overload the appropriate operators:
    Code: C++
    1. template<
    2.   typename T1, typename T2,
    3.   typename Dim, typename T =typename std::common_type<T1, T2>::type
    4. >
    5. Value<Dim, T> operator+(const Value<Dim, T1> a, const Value<Dim, T2> b){
    6.   return Value<Dim, T>(a.getMagnitude()+ b.getMagnitude());
    7. }
    8.  
    9. template<
    10.   typename T1, typename T2,
    11.   typename Dim, typename T =typename std::common_type<T1, T2>::type
    12. >
    13. Value<Dim, T> operator-(const Value<Dim, T1> a, const Value<Dim, T2> b){
    14.   return Value<Dim, T>(a.getMagnitude()- b.getMagnitude());
    15. }
    This code should be pretty straightforward but I think it's worth pointing out that T is the return type between T1 and T2 because we may want to perform calculations on different types such as adding a float to an int. You can read about the semantics of `std::common_type` here[/url]. Multiplication and division look like:
    Code: C++
    1. template<
    2.   typename T1, typename T2,
    3.   typename Dim1, typename Dim2,
    4.   typename T =typename std::common_type<T1, T2>::type
    5. >
    6. Value<typename transform<Dim1, Dim2, lambda<plus>>::type, T>
    7. operator*(const Value<Dim1, T> a, const Value<Dim2, T> b){
    8.   using Dim =typename transform<Dim1, Dim2, lambda<plus>>::type;
    9.   return Value<Dim, T>(a.getMagnitude()* b.getMagnitude());
    10. }
    11.  
    12. template<
    13.   typename T1, typename T2,
    14.   typename Dim1, typename Dim2,
    15.   typename T =typename std::common_type<T1, T2>::type
    16. >
    17. Value<typename transform<Dim1, Dim2, lambda<minus>>::type, T>
    18. operator/(const Value<Dim1, T1> a, const Value<Dim2, T2> b){
    19.   using Dim =typename transform<Dim1, Dim2, lambda<minus>>::type;
    20.   return Value<Dim, T>(a.getMagnitude()/ b.getMagnitude());
    21. }
    (If you are using C++14, you can trim those ugly functions by replacing the really long return type in the function signature with the `auto` keyword, which will tell the compiler to deduce the type for you.)

    Wow, that was quite a lot of work! Now let's give this a try:
    Code: C++
    1. int main(){
    2.   Value<Time, int> time_hr =2;
    3.   Value<Length, int> displacement_km =100+100;
    4.   std::cout<< displacement_km.getMagnitude()<< std::endl;// 200
    5.   return0;
    6. }
    If you compile and run the above code, you should get '200' as output. So that's nice, now what happens if we try and calculate velocity?
    Quote
    $ clang++ -std=c++11 test.cc
    test.cc:78:24: error: no viable conversion from 'Value<typename
          transform<list_c<int, 0, 1, 0, 0, 0, 0, 0>, list_c<int, 0, 0, 1, 0, 0, 0,
          0>, lambda<minus> >::type, [...]>' to 'Value<Velocity, [...]>'
      Value<Velocity, int> velocity = displacement_km / time_hr;
    If you perform the calculation above (or remember the exercise I left to you a while ago) you should notice that the type returned by the transform should be equivalent to the `Velocity` type! What happened was although they have the list elements, they are different types. What we can do to resolve this is by writing a copy constructor in our Value class which takes a different dimension, and checks if they are equal. This is the reason we needed an `equal` metafunction. So our Value class now becomes:
    Code: C++
    1. template<typename Dim, typename T>
    2. class Value {
    3.   T magnitude;
    4. public:
    5.   Value(T magnitude): magnitude(magnitude){}
    6.  
    7.   // Copy constructor added here.
    8.   template<typename OtherDim>
    9.   Value(const Value<OtherDim, T>& rhs): magnitude(rhs.getMagnitude()){
    10.     static_assert(equal<Dim, OtherDim>::type::value, "Assignment from incompatible dimensions.");
    11.   }
    12.  
    13.   template<typename T2>
    14.   Value& operator=(const Value<Dim, T2>& rhs){
    15.     return*this;
    16.   }
    17.  
    18.   T constexpr getMagnitude()const{
    19.     return magnitude;
    20.   }
    21. };
    We use C++11's `static_assert` function here to ensure that `OtherDim` is the same as `Dim`, the dimension we attached to our magnitude. If we didn't have this check, we could calculate whatever nonsense we like, such as dividing a distance and a time and yielding a distance -- defeating the entire purpose of this post! Giving this a try, we finally have implemented working dimensional analysis:
    Code: C++
    1. int main(){
    2.   Value<Time, int> time_hr =2;
    3.   Value<Length, int> displacement_km =100+100;
    4.   Value<Velocity, int> velocity = displacement_km / time_hr;
    5.   std::cout<< velocity.getMagnitude()<< std::endl;// 100
    6.   return0;
    7. }
    RIP

    Offlines1gma

    • Member
    • ****
    • *
    • Posts: 1,234
    • Thanks: +0/-0
      • View Profile
    Re: Introduction to C++ Metaprogramming: Dimensional Analysis
    « Reply #1 on: October 31, 2015, 06:26:08 PM »
    this isnt biology';??
    shellder

    Offlinejustaguy

    • Member
    • ****
    • *
    • Posts: 707
    • Thanks: +0/-0
      • View Profile
    Re: Introduction to C++ Metaprogramming: Dimensional Analysis
    « Reply #2 on: October 31, 2015, 06:39:25 PM »
    this isnt biology';??

    wtf is biology????
    RIP


     

    Copyright © 2017 MoparScape. All rights reserved.
    Powered by SMFPacks SEO Pro Mod |
    SimplePortal 2.3.5 © 2008-2012, SimplePortal