Commit 40d2cd71 authored by CHAMONT David's avatar CHAMONT David
Browse files

Ready for third collective day.

parent 42816c0b
#include <iostream>
#include <cmath>
template< typename UnderlyingType, int s, int m >
class SiUnit
{
public :
explicit constexpr SiUnit( UnderlyingType value ) : my_value{value} {}
explicit constexpr operator UnderlyingType() const { return my_value ; }
private :
UnderlyingType my_value ;
} ;
template< typename UT, int s, int m >
std::ostream & operator<<( std::ostream & os, const SiUnit<UT,s,m> & obj )
{ return (os<<static_cast<UT>(obj)) ; }
template< typename UT, int s1, int m1, int s2, int m2 >
constexpr auto operator*( SiUnit<UT,s1,m1> lhs, SiUnit<UT,s2,m2> rhs )
{ return SiUnit<UT,s1+s2,m1+m2>(static_cast<UT>(lhs)*static_cast<UT>(rhs)) ; }
template< typename UT, int s1, int m1, int s2, int m2 >
constexpr auto operator/( SiUnit<UT,s1,m1> lhs, SiUnit<UT,s2,m2> rhs )
{ return SiUnit<UT,s1-s2,m1-m2>(static_cast<UT>(lhs)/static_cast<UT>(rhs)) ; }
using Time = SiUnit<double,1,0> ;
constexpr Time operator ""_Se ( long double value )
{ return Time{static_cast<double>(value)} ; }
constexpr Time operator ""_Mi ( long double value )
{ return Time{static_cast<double>(value)*60} ; }
constexpr Time operator ""_H ( long double value )
{ return Time{static_cast<double>(value)*60*60} ; }
constexpr Time operator ""_Se ( unsigned long long value )
{ return Time{static_cast<double>(value)} ; }
constexpr Time operator ""_Mi ( unsigned long long value )
{ return Time{static_cast<double>(value)*60} ; }
constexpr Time operator ""_H ( unsigned long long value )
{ return Time{static_cast<double>(value)*60*60} ; }
constexpr Time Se { 1._Se } ;
constexpr Time Mi { 1._Mi } ;
constexpr Time H { 1._H } ;
using Length = SiUnit<double,0,1> ;
constexpr Length operator ""_M ( long double value )
{ return Length{static_cast<double>(value)} ; }
constexpr Length operator ""_KM ( long double value )
{ return Length{static_cast<double>(value)*1000} ; }
constexpr Length operator ""_M ( unsigned long long value )
{ return Length{static_cast<double>(value)} ; }
constexpr Length operator ""_KM ( unsigned long long value )
{ return Length{static_cast<double>(value)*1000} ; }
constexpr Length M { 1._M } ;
constexpr Length KM { 1._KM } ;
using Speed = SiUnit<double,-1,1> ;
std::ostream & operator<<( std::ostream & os, Speed v )
{
constexpr Speed ratio = KM/H ;
return (os<<std::round(static_cast<double>(v/ratio))<<" km/h") ;
}
int main()
{
Length l { 28.0_KM } ;
Time d { 39.0_Mi } ;
Speed vmax = 50_KM/H ;
Speed v = l/d ;
std::cout<<"Max speed : "<<vmax<<std::endl ;
std::cout<<"Mean speed : "<<v<<std::endl ;
}
#include <iostream>
template< typename UnderlyingType, typename TagType >
class StrongTypedef
{
public :
constexpr explicit StrongTypedef( UnderlyingType value ) : my_value{value} {}
constexpr explicit operator UnderlyingType() const { return my_value ; }
private :
UnderlyingType my_value ;
} ;
template< typename UT, typename TT >
constexpr auto operator*( UT lhs, StrongTypedef<UT,TT> rhs )
{ return StrongTypedef<UT,TT>{lhs*static_cast<UT>(rhs)} ; }
template< typename UT, typename TT1, typename TT2 >
constexpr auto operator/( const StrongTypedef<UT,TT1> & lhs, const StrongTypedef<UT,TT2> & rhs )
{ return (static_cast<UT>(lhs)/static_cast<UT>(rhs)) ; }
template< typename UT, typename TT >
auto & operator<<( std::ostream & os, StrongTypedef<UT,TT> data )
{ return (os<<static_cast<UT>(data)) ; }
using Liter = StrongTypedef<double,struct LiterTag> ;
constexpr Liter operator ""_L ( long double value )
{ return Liter(1.*value) ; }
constexpr Liter operator ""_HL ( long double value )
{ return Liter(100.*value) ; }
constexpr Liter operator ""_CL ( long double value )
{ return Liter(0.01*value) ; }
using Meter = StrongTypedef<double,struct MeterTag> ;
constexpr Meter operator ""_M ( long double value )
{ return Meter(.00001*value) ; }
constexpr Meter operator ""_KM ( long double value )
{ return Meter(.01*value) ; }
constexpr Meter operator ""_MM ( long double value )
{ return Meter(0.00000001*value) ; }
int main()
{
constexpr auto fuel { 2.38_L } ;
constexpr auto distance { 28.3_KM } ;
constexpr auto consumption = fuel/(distance/100._KM) ;
std::cout<<consumption<<" l/100km"<<std::endl;
}
\ No newline at end of file
#include <iostream>
template< typename UnderlyingType, typename TagType >
class StrongTypedef
{
public :
explicit StrongTypedef( UnderlyingType value ) : my_value{value} {}
explicit operator UnderlyingType() { return my_value ; }
private :
UnderlyingType my_value ;
} ;
using Km = StrongTypedef<double,struct KmTag> ;
using Liter = StrongTypedef<double,struct LiterTag> ;
class Journey
{
public :
Journey( Km distance, Liter fuel )
: my_distance{distance}, my_fuel{fuel} {}
double consumption() { return static_cast<double>(my_fuel)/static_cast<double>(my_distance)*100. ; }
private :
Km my_distance ;
Liter my_carburant ;
} ;
int main()
{
Km distance { 28 } ;
Liter fuel { 2.38 } ;
Journey t { distance, fuel } ;
std::cout<<t.consumption()<<" l/100km"<<std::endl;
}
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"# Strong typing and International System of Units\n",
"\n",
"<img src=\"img/si-c-logo.png\" width=100px align=\"right\"/>\n",
"\n",
"We generally pretend that we pratice object-oriented programming. However, when we have to choose the type of a new object, we still focus on its content rather than its behaviour, **especially when we deal with scalar numbers**. We choose `float` or `double`, and that's it. For an integer content, we choose `int`, rarely `short`, sometimes `long` if we suspect very big numbers, and that's it. Since **we ignore the meaning** of what we describe, we can **add meters with seconds**, or **confuse a table index with an object identifier**, without any complain from the compiler..."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"C++, especially nowadays, contains helpful functionnalities for the construction of **meaningful types**, for instance for the physical quantities :\n",
"1. [Give units to scalar values](en.1-units.ipynb)\n",
"2. [Handle multiples with user-defined literals](en.2-quantities.ipynb)\n",
"3. [Combine them all](en.3-dimensions.ipynb)\n",
"3. [Centralize constants](en.4-constants.ipynb)\n",
"4. [BUT there are unsolved issues...](en.5-remaining-issues.ipynb)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"## Main ressources and inspirations\n",
"\n",
"Blogs\n",
"* [Strong typedefs, par Jonathan Muller](https://foonathan.net/2016/10/strong-typedefs/)\n",
"* [Strong types, par Jonathan Boccara](https://www.fluentcpp.com/2016/12/08/strong-types-for-strong-interfaces/)\n",
"* [Opaque typedef, par Kyle Markley, 2016](https://sourceforge.net/p/opaque-typedef/wiki/Home/)\n",
"\n",
"Wikipedia\n",
"* [International System of Units](https://en.wikipedia.org/wiki/International_System_of_Units)\n",
"\n",
"Propositions to standardization committee\n",
"* [Opaque Typedefs, P0109](https://wg21.link/p0109)\n",
"* [A C++ Approach to Physical Units, P1935](https://wg21.link/p1935)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"© *CNRS 2020* \n",
"*Assembled and written by David Chamont, translated by Pierre Aubert, this work is made available according to the terms of the* \n",
"[*Creative Commons License - Attribution - NonCommercial - ShareAlike 4.0 International*](http://creativecommons.org/licenses/by-nc-sa/4.0/)"
]
}
],
"metadata": {
"celltoolbar": "Diaporama",
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.5"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"# Strong typing and physical units"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Motivation\n",
"\n",
"There are many situations where the straight usage of builtin types is not safe."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"For example, we could wish to create a circle from a radiss or a diameter :"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"\u001b[1minput_line_35:6:14: \u001b[0m\u001b[0;1;31merror: \u001b[0m\u001b[1mconstructor cannot be redeclared\u001b[0m\n",
" explicit Circle(double diameter) // DOES NOT COMPILE\n",
"\u001b[0;1;32m ^\n",
"\u001b[0m\u001b[1minput_line_35:4:14: \u001b[0m\u001b[0;1;30mnote: \u001b[0mprevious definition is here\u001b[0m\n",
" explicit Circle(double radius)\n",
"\u001b[0;1;32m ^\n",
"\u001b[0m"
]
},
{
"ename": "Interpreter Error",
"evalue": "",
"output_type": "error",
"traceback": [
"Interpreter Error: "
]
}
],
"source": [
"class Circle\n",
" {\n",
" public:\n",
" explicit Circle(double radius)\n",
" : my_radius(radius) {}\n",
" explicit Circle(double diameter)\n",
" : my_radius(diameter / 2) {}\n",
" private:\n",
" double my_radius ;\n",
" } ;"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Other example : let's take a rectangle constructor from one width and height. In the client code, there is no way to distinguish which is width and which is height, and it is (so) easy to swap these values."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class Rectangle\n",
" {\n",
" public:\n",
" Rectangle( double width, double height ) { /*...*/ ; }\n",
" //....\n",
" } ;"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"//...\n",
"Rectangle r(10, 12) ;"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"In both cases (and in many others), we may wish to **create specific types to distinguish these quantities**."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Type aliasing enables to better express the intention, and improve readability."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"using Width = double ;\n",
"using Height = double ;\n",
"\n",
"class Rectangle\n",
" {\n",
" public:\n",
" Rectangle( Width w, Height h ) { /*...*/ ; }\n",
" //....\n",
" } ;\n",
"//...\n",
"Rectangle r(Width{10},Height{12}) ;"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"But from the compiler point of view, **it is still one single type**, and any erroneous mixture is allowed."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"We need new types which are considered different and not commutable by the compiler. The C++ community calls this possible feature **strong typedef** or **opaque typedef**. It was proposed to the normalization committee of C++, but according to last news (2016), Bjarne Stroustrup is not in favor of it..."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Hand-made *strong typedef*\n",
"\n",
"We want, for example, to create a new type which behaves exactly like a `double`, but will be considered as a **different type** by the compiler."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"First steps consist in creating a constructor and a conversion operator from and to `double`. **Let's make them `explicit`**, so to avoid any unwanted implicit conversion:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class Width\n",
" {\n",
" public :\n",
" explicit Width( double value ) : my_value{value} {}\n",
" explicit operator double() const { return my_value ; }\n",
" private :\n",
" double my_value ;\n",
" } ;"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Unfortunately, we cannot do much with this type. Firstly, **all usual operators are missing**, and you can guess how it will be painful to redo all this work each time we want to add a new \"strong alias\"."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Let's use a template\n",
"\n",
"To ease the writing of strong alias, we can obviously use templates."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"This one, beyond constructor and converter, add the `+` operator and `<<` operator:"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"template< typename UnderlyingType >\n",
"class StrongTypedef\n",
" {\n",
" public :\n",
" explicit StrongTypedef( UnderlyingType value ) : my_value{value} {}\n",
" explicit operator UnderlyingType() const { return my_value ; }\n",
" friend StrongTypedef operator+( StrongTypedef lhs, StrongTypedef rhs )\n",
" { return StrongTypedef(lhs.my_value+rhs.my_value) ; }\n",
" friend std::ostream & operator<<( std::ostream & os, StrongTypedef v )\n",
" { return (os<<v.my_value) ; }\n",
" private :\n",
" UnderlyingType my_value ;\n",
" } ;\n",
"\n",
"using Width = StrongTypedef<double> ;\n",
"using Height = StrongTypedef<double> ;"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Well, from the compiler point of view, `Width` and `Height` are again the same single type `StrongTypedef<double>`. Let's add a second type parameter, whose single utility is to make them somehow different and not commutable."
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"template< typename UnderlyingType, typename TagType >\n",
"class StrongTypedef\n",
" {\n",
" public :\n",
" explicit StrongTypedef( UnderlyingType value ) : my_value{value} {}\n",
" explicit operator UnderlyingType() const { return my_value ; }\n",
" friend StrongTypedef operator+( StrongTypedef v1, StrongTypedef v2 )\n",