Commit b3a679ea authored by CHAMONT David's avatar CHAMONT David
Browse files

Add 5-Collective

parent d9859e2a
#include <iostream>
#include <cmath>
template< typename UnderlyingType, int s, int m >
class SiUnit
{
public :
explicit constexpr SiUnit( UnderlyingType value ) : value_{value} {}
explicit constexpr operator UnderlyingType() const { return value_ ; }
private :
UnderlyingType 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(value) ; }
constexpr Time operator ""_Mi ( long double value )
{ return Time(value*60) ; }
constexpr Time operator ""_H ( long double value )
{ return Time(value*60*60) ; }
constexpr Time operator ""_Se ( unsigned long long value )
{ return Time(value) ; }
constexpr Time operator ""_Mi ( unsigned long long value )
{ return Time(value*60) ; }
constexpr Time operator ""_H ( unsigned long long value )
{ return Time(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(value) ; }
constexpr Length operator ""_KM ( long double value )
{ return Length(value*1000) ; }
constexpr Length operator ""_M ( unsigned long long value )
{ return Length(value) ; }
constexpr Length operator ""_KM ( unsigned long long value )
{ return Length(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 ) : value_{value} {}
constexpr explicit operator UnderlyingType() const { return value_ ; }
private :
UnderlyingType 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 ) : value_{value} {}
explicit operator UnderlyingType() { return value_ ; }
private :
UnderlyingType value_ ;
} ;
using Km = StrongTypedef<double,struct KmTag> ;
using Liter = StrongTypedef<double,struct LiterTag> ;
class Journey
{
public :
Journey( Km distance, Liter carburant )
: distance_{distance}, carburant_{carburant} {}
double consumption() { return static_cast<double>(carburant_)/static_cast<double>(distance_)*100. ; }
private :
Km distance_ ;
Liter carburant_ ;
} ;
int main()
{
Km distance { 28 } ;
Liter carburant { 2.38 } ;
Journey t { distance, carburant } ;
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",
"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 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": "C++17",
"language": "C++17",
"name": "xcpp17"
},
"language_info": {
"codemirror_mode": "text/x-c++src",
"file_extension": ".cpp",
"mimetype": "text/x-c++src",
"name": "c++",
"version": "17"
}
},
"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 satisfying."
]
},
{
"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": null,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class Circle\n",
" {\n",
" public:\n",
" explicit Circle(double radius)\n",
" : radius_(radius) {}\n",
" explicit Circle(double diameter) // DOES NOT COMPILE\n",
" : radius_(diameter / 2) {}\n",
" //...\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": null,
"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": null,
"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 would like 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": null,
"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": "code",
"execution_count": null,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"But from the compiler point of view, **it is still one single type**, and any 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 favorable."
]
},
{
"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 svoid any unwanted implicit conversion:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class Width\n",
" {\n",
" public :\n",
" explicit Width( double value ) : value_{value} {}\n",
" explicit operator double() const { return value_ ; }\n",
" private :\n",
" double 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": null,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"template< typename UnderlyingType >\n",
"class StrongTypedef\n",
" {\n",
" public :\n",
" explicit StrongTypedef( UnderlyingType value ) : value_{value} {}\n",
" explicit operator UnderlyingType() const { return value_ ; }\n",
" friend StrongTypedef operator+( StrongTypedef lhs, StrongTypedef rhs )\n",
" { return StrongTypedef(lhs.value_+rhs.value_) ; }\n",
" friend std::ostream & operator<<( std::ostream & os, StrongTypedef v )\n",
" { return (os<<v.value_) ; }\n",
" private :\n",
" UnderlyingType 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": null,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"template< typename UnderlyingType, typename TagType >\n",
"class StrongTypedef\n",
" {\n",
" public :\n",
" explicit StrongTypedef( UnderlyingType value ) : value_{value} {}\n",
" explicit operator UnderlyingType() const { return value_ ; }\n",
" friend StrongTypedef operator+( StrongTypedef v1, StrongTypedef v2 )\n",
" { return StrongTypedef(v1.value_+v2.value_) ; }\n",
" friend std::ostream & operator<<( std::ostream & os, StrongTypedef v )\n",
" { return (os<<v.value_) ; }\n",
" private :\n",
" UnderlyingType value_ ;\n",
" } ;\n",
"\n",
"struct WidthTag {} ;\n",
"struct HeightTag {} ;\n",
"\n",
"using Width = StrongTypedef<double,WidthTag> ;\n",
"using Height = StrongTypedef<double,HeightTag> ;"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Void structs `WidthTag` and `HeightTag` aims to differenciate between `Width` et `Height`. There are usually called *Tag Types* or *Phantom Types*. We can even create them on the fly :"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"using Width = StrongTypedef<double,struct WidthTag> ;\n",
"using Height = StrongTypedef<double,struct HeightTag> ;"
]
},
{