strongly-typed SI-Sytem



  • Hab ne kleine Klasse zum konsistenten Rechnen mit physikalischen Einheiten geschrieben. Einige Operatoren mit Barton-Nackman und crtp implementiert. Zur compile-time soll der Code das Verrechnen unsinniger Einheiten miteinander abfangen. Funktioniert auch alles soweit

    https://godbolt.org/z/KWdYTt

    #include <iostream>
    
    
    template<typename Value>
    struct OperatorFacade {
      friend constexpr bool operator!=(Value const &lhs, Value const &rhs)
      noexcept {
        return !(lhs==rhs);
      }
      friend constexpr bool operator>(Value const &lhs, Value const &rhs) noexcept {
        return rhs < lhs;
      }
      friend constexpr bool operator<=(Value const &lhs, Value const &rhs)
      noexcept {
        return !(rhs > lhs);
      }
      friend constexpr bool operator>=(Value const &lhs, Value const &rhs)
      noexcept {
        return !(rhs < lhs);
      }
      friend constexpr auto &operator<<(std::ostream &os, Value const &other)
      noexcept {
        return os << static_cast<long double>(other);
      }
      friend constexpr auto operator-(Value const &lhs,
                                      Value const &rhs) noexcept {
        return Value{lhs} -= rhs;
      }
      friend constexpr auto operator+(Value const &lhs,
                                      Value const &rhs) noexcept {
        return Value{lhs} += rhs;
      }
    };
    
    // Type-safety at compile-time
    template<int M = 0, int K = 0, int S = 0>
    struct MksUnit {
      enum { metre = M, kilogram = K, second = S };
    };
    
    template<typename U = MksUnit<>> // default to dimensionless value
    class Value final : public OperatorFacade<Value<U>> {
     public:
      Value(Value const &other) : OperatorFacade<Value>(other) {
        std::cerr << "Copy ctor" << std::endl;
      }
      Value(Value &&other) noexcept : OperatorFacade<Value>(std::move(other)) {
        std::cerr << "Move ctor" << std::endl;
      }
      auto &operator=(Value const &other) {
        std::cerr << "copy assign" << std::endl;
        magnitude_ = other.magnitude_;
        return *this;
      }
      auto &operator=(Value &&other) noexcept {
        std::cerr << "Move assign " << std::endl;
        magnitude_ = std::move(other.magnitude_);
        return *this;
      }
    
      constexpr explicit Value() noexcept = default;
      constexpr explicit Value(long double const &magnitude) noexcept
          : magnitude_{magnitude} {}
      //constexpr auto &magnitude()  noexcept { return magnitude_; }
      constexpr explicit operator long double() const noexcept {
        return magnitude_;
      }
    
      friend constexpr bool operator==(Value const &lhs, Value const &rhs)noexcept {
        return lhs.magnitude_==rhs.magnitude_;
      }
      friend constexpr bool operator<(Value const &lhs, Value const &rhs)noexcept {
        return lhs < rhs;
      }
    
      constexpr auto &operator+=(Value const &other) noexcept {
        magnitude_ += other.magnitude_;
        return *this;
      }
      constexpr auto &operator-=(Value const &other) noexcept {
        magnitude_ -= other.magnitude_;
        return *this;
      }
      friend constexpr auto operator*(long double const &scalar, Value const &other)
      noexcept {
        return other*scalar;
      }
      constexpr auto operator*(long double const &scalar) const noexcept {
        return Value{magnitude_*scalar};
      }
      constexpr auto &operator*=(long double const &scalar) noexcept {
        magnitude_ *= scalar;
        return *this;
      }
    
     private:
      long double magnitude_{0.0};
    };
    
    // Some handy alias declarations
    using Length = Value<MksUnit<1, 0, 0>>;
    
    
    namespace si {
    // A couple of convenient factory functions
    constexpr auto operator "" _m(long double magnitude)noexcept {
      return Length{magnitude};
    }
    }
    
    // Arithmetic operators for consistent type-rich conversions of SI-Units
    template<int M1, int K1, int S1, int M2, int K2, int S2>
    constexpr auto operator*(Value<MksUnit<M1, K1, S1>> const &lhs,
                             Value<MksUnit<M2, K2, S2>> const &rhs) noexcept {
      return Value<MksUnit<M1 + M2, K1 + K2, S1 + S2>>{
          static_cast<long double>(lhs)*static_cast<long double>(rhs)};
    }
    
    template<int M1, int K1, int S1, int M2, int K2, int S2>
    constexpr auto operator/(Value<MksUnit<M1, K1, S1>> const &lhs,
                             Value<MksUnit<M2, K2, S2>> const &rhs) noexcept {
      return Value<MksUnit<M1 - M2, K1 - K2, S1 - S2>>{
          static_cast<long double>(lhs)/static_cast<long double>(rhs)};
    }
    
    
    
    int main(){
      Length l1 {10};
      Length l2 {20};
      l1 + l2;
    }
    
    
    1. Die special member function habe ich dekoriert, um eventuelle kopien zu entdecken. Und tatsächlich habe ich zwei Kopien bei der obigen Addition, obwohl ich nur eine erwartet hätte. Habe ich einen Fehler im Code?

    2. Entdeckt ihr irgendwelche Fehler oder habt Verbesserungsvorschläge bzgl. effizienz, performanz, Überladungen, Delegierungen etc.?

    3. Verhindere ich hier irgendwo implizit die move operationen, sodass stattdessen Kopien gemacht werden?

    Danke schon mal



  • @sewing sagte in strongly-typed SI-Sytem:

    Verhindere ich hier irgendwo implizit die move operationen, sodass stattdessen Kopien gemacht werden?

    Ja. rvalue + lvalue ist OK. lvalue + rvalue kopiert aber unnötigerweise.
    Bei Multiplikation mit nem Skalar versuchst du nichtmal optimal zu sein.

    Entdeckt ihr irgendwelche Fehler oder habt Verbesserungsvorschläge bzgl. effizienz, performanz, Überladungen, Delegierungen etc.?

    Ja, z.B. die Multiplikationsoperatoren geben Referenzen auf Temporaries zurück.



  • @hustbaer sagte in strongly-typed SI-Sytem:

    Entdeckt ihr irgendwelche Fehler oder habt Verbesserungsvorschläge bzgl. effizienz, performanz, Überladungen, Delegierungen etc.?
    Ja, z.B. die Multiplikationsoperatoren geben Referenzen auf Temporaries zurück.

    das war Absicht, und seltsamerweise schmiss mein compiler keinen errror

    @hustbaer sagte in strongly-typed SI-Sytem:

    Ja. rvalue + lvalue ist OK. lvalue + rvalue kopiert aber unnötigerweise.

    wie verhindere ich das? Muss ja in jedem Fall nen local object allokieren...

    Hab den Code mal upgedated. Dachte die symmatrischen Additionen sollte man in terms of der compound addition schreiben. Aber dann kopiert man halt immer...



  • @sewing sagte in strongly-typed SI-Sytem:

    @hustbaer sagte in strongly-typed SI-Sytem:

    Entdeckt ihr irgendwelche Fehler oder habt Verbesserungsvorschläge bzgl. effizienz, performanz, Überladungen, Delegierungen etc.?
    Ja, z.B. die Multiplikationsoperatoren geben Referenzen auf Temporaries zurück.

    das war Absicht, und seltsamerweise schmiss mein compiler keinen errror

    Man darf ja Memberfunktionen auf Rvalues aufrufen so lange diese keinen ref-qualifier haben. Und da *= eine Memberfunktionen ist, darf sie auf das Temporary aufgerufen werden. Und daher kompiliert das fehlerfrei. Alles völlig standardkonform.

    @hustbaer sagte in strongly-typed SI-Sytem:

    Ja. rvalue + lvalue ist OK. lvalue + rvalue kopiert aber unnötigerweise.

    wie verhindere ich das? Muss ja in jedem Fall nen local object allokieren...

    Hab den Code mal upgedated. Dachte die symmatrischen Additionen sollte man in terms of der compound addition schreiben. Aber dann kopiert man halt immer...

    Naja kommt drauf an ob deine Klasse davon ausgeht dass a + b == b + a gilt. Falls ja, dann kannst du mehrere Overloads machen die dann a += b bzw. b += a verwenden. Damit nichts ambiguous wird wirst du dafür aber vermutlich 4 Overloads pro Operator brauchen.

    Die Frage ist nur ob sich das überhaupt auszahlt. So lange keine BigNum Klassen mit dynamischem Speicher o.ä. verwendet werden wohl eher nicht.



  • danke hustbaer für deine Hilfe, weiß ich sehr zu schätzen!


Log in to reply