Skip to main content
edited tags; edited tags
Link
200_success
  • 145.6k
  • 22
  • 191
  • 481
Rollback to Revision 3
Source Link
hoffmale
  • 6.5k
  • 18
  • 41
  #include <iostream><string>
 #include <sstream>

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 &otherother)
  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 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 static_cast<long double>(lhs.magnitude_==rhs.magnitude_;)==static_cast<long double>(rhs);
  }
  friend constexpr bool operator<(Value const &lhs, Value const &rhs)noexcept {
    return static_cast<long double>(lhs) < rhs;static_cast<long double>(rhs);
  }

  constexpr auto &operator+=(Value const &other) noexcept {
    magnitude_ += static_cast<long double>(other.magnitude_;);
    return *this;
  }
  constexpr auto &operator-=(Value const &other) noexcept {
    auto s = other.magnitude_;
    magnitude_ -= static_cast<long double>(other.magnitude_;);
    return *this;
  }
  friend constexpr auto operator*const &operator*(long double scalar, Value const &other)
  noexceptconst {
    return other*scalar;
  }
  constexpr auto operator*(long double scalar) constmagnitude_ noexcept*= {scalar;
    return Value{magnitude_*scalar};*this;
  }
  constexprfriend auto &operator*=&operator*(long double scalar) noexcept {
  , Value magnitude_const *=&other) scalar;{
    return *this;other.operator*(scalar);
  }

 private:
  long double mutable magnitude_{0.0};
 
};

// Some handy alias declarations
using DimensionlessQuantity = Value<>;
using Length = Value<MksUnit<1, 0, 0>>;
using MassArea = Value<MksUnit<0Value<MksUnit<2, 10, 0>>;
using TimeVolume = Value<MksUnit<0Value<MksUnit<3, 0, 1>>;0>>;
using VelocityMass = Value<MksUnit<1Value<MksUnit<0, 01, -1>>;0>>;
using AccelerationTime = Value<MksUnit<1Value<MksUnit<0, 0, -2>>;1>>;
using AreaVelocity = Value<MksUnit<2Value<MksUnit<1, 0, 0>>;-1>>;
using VolumeAcceleration = Value<MksUnit<3Value<MksUnit<1, 0, 0>>;-2>>;
using Frequency = Value<MksUnit<0, 0, -1>>;
using Force = Value<MksUnit<1, 1, -2>>;
using Pressure = Value<MksUnit<-1, 1, -2>>;
using Momentum = Value<MksUnit<1, 1, -1>>;
using Work = Value<MksUnit<2, 1, -2>>;
using Power = Value<MksUnit<2, 1, -3>>;

namespace si {
// A couple of convenient factory functions
constexpr auto operator "" _N(long double magnitude) noexcept {
  return Force{magnitude};
}
constexpr auto operator "" _ms2(long double magnitude)noexcept {
  return Acceleration{magnitude};
}
constexpr auto operator "" _s(long double magnitude) noexcept {
  return Time{magnitude};
}
constexpr auto operator "" _Ns(long double magnitude)noexcept {
  return Momentum{magnitude};
}
constexpr auto operator "" _m(long double magnitude)noexcept {
  return Length{magnitude};
}
constexpr auto operator "" _ms(long double magnitude)noexcept {
  return Velocity{magnitude};
}
constexpr auto operator "" _kg(long double magnitude)noexcept {
  return Mass{magnitude};
}
constexpr auto operator "" _1s(long double magnitude)noexcept {
  return Frequency{magnitude};
}
constexpr auto operator "" _Nm(long double magnitude)noexcept {
  return Work{magnitude};
}
constexpr auto operator "" _W(long double magnitude)noexcept {
  return Power{magnitude};
}
// Scientific constants
auto constexpr speedOfLight = 299792458.0_ms;
auto constexpr gravitationalAccelerationOnEarth = 9.80665_ms2;
}
// 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)};
}

// Scientific constants
auto constexpr speedOfLight = 299792458.0_ms;
auto constexpr gravitationalAccelerationOnEarth = 9.80665_ms2;

void applyMomentumToSpacecraftBody(Momentum const &impulseValue) {};


int main(){
std::cout << "Consistent? " << 10.0_ms - 5.0_m << std::endl;
}
    

Edit: thank you for your valuable and detailed input. I have fixed all the issues you mentioned and updated the code above. Regarding the general stuff section. Most of those points are still on the agenda and will follow as I see fit. I do have some additional question:

  1. Is this code ok from a stylistic, code-technical point of view or are there other better means to implement such functionality?

  2. Are the overloads that enable multiplications with a scalar from left and right the only way to enable this behavior or are there other possibilities?

  3. I dont know if addition is optimal as there seem to be too many copies when I write code like

Length l1{10.0}; 
Length s = Length{7} - l1;
  #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 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 {
    auto s = other.magnitude_;
    magnitude_ -= other.magnitude_;
    return *this;
  }
  friend constexpr auto operator*(long double scalar, Value const &other)
  noexcept {
    return other*scalar;
  }
  constexpr auto operator*(long double scalar) const noexcept {
    return Value{magnitude_*scalar};
  }
  constexpr auto &operator*=(long double scalar) noexcept {
    magnitude_ *= scalar;
    return *this;
  }

 private:
  long double magnitude_{0.0};
 
};

// Some handy alias declarations
using DimensionlessQuantity = Value<>;
using Length = Value<MksUnit<1, 0, 0>>;
using Mass = Value<MksUnit<0, 1, 0>>;
using Time = Value<MksUnit<0, 0, 1>>;
using Velocity = Value<MksUnit<1, 0, -1>>;
using Acceleration = Value<MksUnit<1, 0, -2>>;
using Area = Value<MksUnit<2, 0, 0>>;
using Volume = Value<MksUnit<3, 0, 0>>;
using Frequency = Value<MksUnit<0, 0, -1>>;
using Force = Value<MksUnit<1, 1, -2>>;
using Pressure = Value<MksUnit<-1, 1, -2>>;
using Momentum = Value<MksUnit<1, 1, -1>>;
using Work = Value<MksUnit<2, 1, -2>>;
using Power = Value<MksUnit<2, 1, -3>>;

namespace si {
// A couple of convenient factory functions
constexpr auto operator "" _N(long double magnitude) noexcept {
  return Force{magnitude};
}
constexpr auto operator "" _ms2(long double magnitude)noexcept {
  return Acceleration{magnitude};
}
constexpr auto operator "" _s(long double magnitude) noexcept {
  return Time{magnitude};
}
constexpr auto operator "" _Ns(long double magnitude)noexcept {
  return Momentum{magnitude};
}
constexpr auto operator "" _m(long double magnitude)noexcept {
  return Length{magnitude};
}
constexpr auto operator "" _ms(long double magnitude)noexcept {
  return Velocity{magnitude};
}
constexpr auto operator "" _kg(long double magnitude)noexcept {
  return Mass{magnitude};
}
constexpr auto operator "" _1s(long double magnitude)noexcept {
  return Frequency{magnitude};
}
constexpr auto operator "" _Nm(long double magnitude)noexcept {
  return Work{magnitude};
}
constexpr auto operator "" _W(long double magnitude)noexcept {
  return Power{magnitude};
}
// Scientific constants
auto constexpr speedOfLight = 299792458.0_ms;
auto constexpr gravitationalAccelerationOnEarth = 9.80665_ms2;
}
// 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)};
}
        

Edit: thank you for your valuable and detailed input. I have fixed all the issues you mentioned and updated the code above. Regarding the general stuff section. Most of those points are still on the agenda and will follow as I see fit. I do have some additional question:

  1. Is this code ok from a stylistic, code-technical point of view or are there other better means to implement such functionality?

  2. Are the overloads that enable multiplications with a scalar from left and right the only way to enable this behavior or are there other possibilities?

  3. I dont know if addition is optimal as there seem to be too many copies when I write code like

Length l1{10.0}; 
Length s = Length{7} - l1;
#include <string>
#include <sstream>

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:
  constexpr explicit Value() noexcept = default;
  constexpr explicit Value(long double magnitude) noexcept
      : magnitude_{magnitude} {}
  //constexpr auto &magnitude()  noexcept { return magnitude_; }
  constexpr explicit operator long double() const noexcept {
    return
        magnitude_;
  }

  friend bool operator==(Value const &lhs, Value const &rhs) {
    return static_cast<long double>(lhs)==static_cast<long double>(rhs);
  }
  friend bool operator<(Value const &lhs, Value const &rhs) {
    return static_cast<long double>(lhs) < static_cast<long double>(rhs);
  }

  auto &operator+=(Value const &other) {
    magnitude_ += static_cast<long double>(other);
    return *this;
  }
  auto &operator-=(Value const &other) {
    magnitude_ -= static_cast<long double>(other);
    return *this;
  }
  auto const &operator*(long double scalar) const {
    magnitude_ *= scalar;
    return *this;
  }
  friend auto &operator*(long double scalar, Value const &other) {
    return other.operator*(scalar);
  }

 private:
  long double mutable magnitude_{0.0};
};

// Some handy alias declarations
using DimensionlessQuantity = Value<>;
using Length = Value<MksUnit<1, 0, 0>>;
using Area = Value<MksUnit<2, 0, 0>>;
using Volume = Value<MksUnit<3, 0, 0>>;
using Mass = Value<MksUnit<0, 1, 0>>;
using Time = Value<MksUnit<0, 0, 1>>;
using Velocity = Value<MksUnit<1, 0, -1>>;
using Acceleration = Value<MksUnit<1, 0, -2>>;
using Frequency = Value<MksUnit<0, 0, -1>>;
using Force = Value<MksUnit<1, 1, -2>>;
using Pressure = Value<MksUnit<-1, 1, -2>>;
using Momentum = Value<MksUnit<1, 1, -1>>;

// A couple of convenient factory functions
constexpr auto operator "" _N(long double magnitude) {
  return Force{magnitude};
}
constexpr auto operator "" _ms2(long double magnitude) {
  return Acceleration{magnitude};
}
constexpr auto operator "" _s(long double magnitude) {
  return Time{magnitude};
}
constexpr auto operator "" _Ns(long double magnitude) {
  return Momentum{magnitude};
}
constexpr auto operator "" _m(long double magnitude) {
  return Length{magnitude};
}
constexpr auto operator "" _ms(long double magnitude) {
  return Velocity{magnitude};
}
constexpr auto operator "" _kg(long double magnitude) {
  return Mass{magnitude};
}
constexpr auto operator "" _1s(long double magnitude) {
  return Frequency{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)};
}

// Scientific constants
auto constexpr speedOfLight = 299792458.0_ms;
auto constexpr gravitationalAccelerationOnEarth = 9.80665_ms2;

void applyMomentumToSpacecraftBody(Momentum const &impulseValue) {};


int main(){
std::cout << "Consistent? " << 10.0_ms - 5.0_m << std::endl;
}
    
added 760 characters in body
Source Link
CD86
  • 173
  • 3
#include <string>
 #include <sstream>
<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&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 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 static_cast<long double>(lhs)==static_cast<long double>(rhs);.magnitude_==rhs.magnitude_;
  }
  friend constexpr bool operator<(Value const &lhs, Value const &rhs)noexcept {
    return static_cast<long double>(lhs) < static_cast<long double>(rhs);rhs;
  }

  constexpr auto &operator+=(Value const &other) noexcept {
    magnitude_ += static_cast<long double>(other);.magnitude_;
    return *this;
  }
  constexpr auto &operator-=(Value const &other) noexcept {
    auto s = other.magnitude_;
    magnitude_ -= static_cast<long double>(other);.magnitude_;
    return *this;
  }
  autofriend constconstexpr &operator*auto operator*(long double scalar), Value const {&other)
    magnitude_ *=noexcept scalar;{
    return *this;other*scalar;
  }
  friendconstexpr auto &operator*operator*(long double scalar, Value) const &other)noexcept {
    return other.operator*Value{magnitude_*scalar};
  }
  constexpr auto &operator*=(long double scalar); noexcept {
    magnitude_ *= scalar;
    return *this;
  }

 private:
  long double mutable magnitude_{0.0}; 

};

// Some handy alias declarations
using DimensionlessQuantity = Value<>;
using Length = Value<MksUnit<1, 0, 0>>;
using AreaMass = Value<MksUnit<2Value<MksUnit<0, 01, 0>>;
using VolumeTime = Value<MksUnit<3Value<MksUnit<0, 0, 0>>;1>>;
using MassVelocity = Value<MksUnit<0Value<MksUnit<1, 10, 0>>;-1>>;
using TimeAcceleration = Value<MksUnit<0Value<MksUnit<1, 0, 1>>;-2>>;
using VelocityArea = Value<MksUnit<1Value<MksUnit<2, 0, -1>>;0>>;
using AccelerationVolume = Value<MksUnit<1Value<MksUnit<3, 0, -2>>;0>>;
using Frequency = Value<MksUnit<0, 0, -1>>;
using Force = Value<MksUnit<1, 1, -2>>;
using Pressure = Value<MksUnit<-1, 1, -2>>;
using Momentum = Value<MksUnit<1, 1, -1>>;
using Work = Value<MksUnit<2, 1, -2>>;
using Power = Value<MksUnit<2, 1, -3>>;

namespace si {
// A couple of convenient factory functions
constexpr auto operator "" _N(long double magnitude) noexcept {
  return Force{magnitude};
}
constexpr auto operator "" _ms2(long double magnitude)noexcept {
  return Acceleration{magnitude};
}
constexpr auto operator "" _s(long double magnitude) noexcept {
  return Time{magnitude};
}
constexpr auto operator "" _Ns(long double magnitude)noexcept {
  return Momentum{magnitude};
}
constexpr auto operator "" _m(long double magnitude)noexcept {
  return Length{magnitude};
}
constexpr auto operator "" _ms(long double magnitude)noexcept {
  return Velocity{magnitude};
}
constexpr auto operator "" _kg(long double magnitude)noexcept {
  return Mass{magnitude};
}
constexpr auto operator "" _1s(long double magnitude)noexcept {
  return Frequency{magnitude};
}
constexpr auto operator "" _Nm(long double magnitude)noexcept {
  return Work{magnitude};
}
constexpr auto operator "" _W(long double magnitude)noexcept {
  return Power{magnitude};
}
// Scientific constants
auto constexpr speedOfLight = 299792458.0_ms;
auto constexpr gravitationalAccelerationOnEarth = 9.80665_ms2;
}
// 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)};
}

// Scientific constants
auto constexpr speedOfLight = 299792458.0_ms;
auto constexpr gravitationalAccelerationOnEarth = 9.80665_ms2;

void applyMomentumToSpacecraftBody(Momentum const &impulseValue) {};


int main(){
std::cout << "Consistent? " << 10.0_ms - 5.0_m << std::endl;
}
    

Edit: thank you for your valuable and detailed input. I have fixed all the issues you mentioned and updated the code above. Regarding the general stuff section. Most of those points are still on the agenda and will follow as I see fit. I do have some additional question:

  1. Is this code ok from a stylistic, code-technical point of view or are there other better means to implement such functionality?

  2. Are the overloads that enable multiplications with a scalar from left and right the only way to enable this behavior or are there other possibilities?

  3. I dont know if addition is optimal as there seem to be too many copies when I write code like

Length l1{10.0}; 
Length s = Length{7} - l1;
#include <string>
#include <sstream>

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:
  constexpr explicit Value() noexcept = default;
  constexpr explicit Value(long double magnitude) noexcept
      : magnitude_{magnitude} {}
  //constexpr auto &magnitude()  noexcept { return magnitude_; }
  constexpr explicit operator long double() const noexcept {
    return
        magnitude_;
  }

  friend bool operator==(Value const &lhs, Value const &rhs) {
    return static_cast<long double>(lhs)==static_cast<long double>(rhs);
  }
  friend bool operator<(Value const &lhs, Value const &rhs) {
    return static_cast<long double>(lhs) < static_cast<long double>(rhs);
  }

  auto &operator+=(Value const &other) {
    magnitude_ += static_cast<long double>(other);
    return *this;
  }
  auto &operator-=(Value const &other) {
    magnitude_ -= static_cast<long double>(other);
    return *this;
  }
  auto const &operator*(long double scalar) const {
    magnitude_ *= scalar;
    return *this;
  }
  friend auto &operator*(long double scalar, Value const &other) {
    return other.operator*(scalar);
  }

 private:
  long double mutable magnitude_{0.0};
};

// Some handy alias declarations
using DimensionlessQuantity = Value<>;
using Length = Value<MksUnit<1, 0, 0>>;
using Area = Value<MksUnit<2, 0, 0>>;
using Volume = Value<MksUnit<3, 0, 0>>;
using Mass = Value<MksUnit<0, 1, 0>>;
using Time = Value<MksUnit<0, 0, 1>>;
using Velocity = Value<MksUnit<1, 0, -1>>;
using Acceleration = Value<MksUnit<1, 0, -2>>;
using Frequency = Value<MksUnit<0, 0, -1>>;
using Force = Value<MksUnit<1, 1, -2>>;
using Pressure = Value<MksUnit<-1, 1, -2>>;
using Momentum = Value<MksUnit<1, 1, -1>>;

// A couple of convenient factory functions
constexpr auto operator "" _N(long double magnitude) {
  return Force{magnitude};
}
constexpr auto operator "" _ms2(long double magnitude) {
  return Acceleration{magnitude};
}
constexpr auto operator "" _s(long double magnitude) {
  return Time{magnitude};
}
constexpr auto operator "" _Ns(long double magnitude) {
  return Momentum{magnitude};
}
constexpr auto operator "" _m(long double magnitude) {
  return Length{magnitude};
}
constexpr auto operator "" _ms(long double magnitude) {
  return Velocity{magnitude};
}
constexpr auto operator "" _kg(long double magnitude) {
  return Mass{magnitude};
}
constexpr auto operator "" _1s(long double magnitude) {
  return Frequency{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)};
}

// Scientific constants
auto constexpr speedOfLight = 299792458.0_ms;
auto constexpr gravitationalAccelerationOnEarth = 9.80665_ms2;

void applyMomentumToSpacecraftBody(Momentum const &impulseValue) {};


int main(){
std::cout << "Consistent? " << 10.0_ms - 5.0_m << std::endl;
}
    
  #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 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 {
    auto s = other.magnitude_;
    magnitude_ -= other.magnitude_;
    return *this;
  }
  friend constexpr auto operator*(long double scalar, Value const &other)
  noexcept {
    return other*scalar;
  }
  constexpr auto operator*(long double scalar) const noexcept {
    return Value{magnitude_*scalar};
  }
  constexpr auto &operator*=(long double scalar) noexcept {
    magnitude_ *= scalar;
    return *this;
  }

 private:
  long double magnitude_{0.0}; 

};

// Some handy alias declarations
using DimensionlessQuantity = Value<>;
using Length = Value<MksUnit<1, 0, 0>>;
using Mass = Value<MksUnit<0, 1, 0>>;
using Time = Value<MksUnit<0, 0, 1>>;
using Velocity = Value<MksUnit<1, 0, -1>>;
using Acceleration = Value<MksUnit<1, 0, -2>>;
using Area = Value<MksUnit<2, 0, 0>>;
using Volume = Value<MksUnit<3, 0, 0>>;
using Frequency = Value<MksUnit<0, 0, -1>>;
using Force = Value<MksUnit<1, 1, -2>>;
using Pressure = Value<MksUnit<-1, 1, -2>>;
using Momentum = Value<MksUnit<1, 1, -1>>;
using Work = Value<MksUnit<2, 1, -2>>;
using Power = Value<MksUnit<2, 1, -3>>;

namespace si {
// A couple of convenient factory functions
constexpr auto operator "" _N(long double magnitude) noexcept {
  return Force{magnitude};
}
constexpr auto operator "" _ms2(long double magnitude)noexcept {
  return Acceleration{magnitude};
}
constexpr auto operator "" _s(long double magnitude) noexcept {
  return Time{magnitude};
}
constexpr auto operator "" _Ns(long double magnitude)noexcept {
  return Momentum{magnitude};
}
constexpr auto operator "" _m(long double magnitude)noexcept {
  return Length{magnitude};
}
constexpr auto operator "" _ms(long double magnitude)noexcept {
  return Velocity{magnitude};
}
constexpr auto operator "" _kg(long double magnitude)noexcept {
  return Mass{magnitude};
}
constexpr auto operator "" _1s(long double magnitude)noexcept {
  return Frequency{magnitude};
}
constexpr auto operator "" _Nm(long double magnitude)noexcept {
  return Work{magnitude};
}
constexpr auto operator "" _W(long double magnitude)noexcept {
  return Power{magnitude};
}
// Scientific constants
auto constexpr speedOfLight = 299792458.0_ms;
auto constexpr gravitationalAccelerationOnEarth = 9.80665_ms2;
}
// 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)};
}
        

Edit: thank you for your valuable and detailed input. I have fixed all the issues you mentioned and updated the code above. Regarding the general stuff section. Most of those points are still on the agenda and will follow as I see fit. I do have some additional question:

  1. Is this code ok from a stylistic, code-technical point of view or are there other better means to implement such functionality?

  2. Are the overloads that enable multiplications with a scalar from left and right the only way to enable this behavior or are there other possibilities?

  3. I dont know if addition is optimal as there seem to be too many copies when I write code like

Length l1{10.0}; 
Length s = Length{7} - l1;
Spelling, grammar, link
Source Link
Toby Speight
  • 88.4k
  • 14
  • 104
  • 327
Loading
edited tags
Link
Martin R
  • 24.2k
  • 2
  • 38
  • 96
Loading
Source Link
CD86
  • 173
  • 3
Loading