From 345480d2c0647a1a4bf8b7915d78155a261ea988 Mon Sep 17 00:00:00 2001 From: Bent Bisballe Nyeng Date: Thu, 3 Aug 2023 17:39:59 +0200 Subject: A5: Final --- a5/exercise.tex | 139 ++++++++++++++++++++++++++++++++++++++++++-------------- a5/matrix.cc | 8 ++++ 2 files changed, 112 insertions(+), 35 deletions(-) diff --git a/a5/exercise.tex b/a5/exercise.tex index 9491afe..2544cff 100644 --- a/a5/exercise.tex +++ b/a5/exercise.tex @@ -1,18 +1,24 @@ \title{A5: Generic Programming} \input{preamble.tex} -I decided to use std container for the elements of the matrix and -initially went with \texttt{std::vector}. But since we were going -to do arithmentics on the data, and no requirements were made as to -the arithmentics actually being those of a real matrix, I figure why -not use \texttt{std::valarray} and have all arithmentic -operations be per-entry, re-suing the behaviour of -\texttt{std::valarray} directly. - -In the change I discovered that \texttt{std::valarray} and +In this exercise a matrix class is being developed and gradually +evolved into a typesafe templated component. + +\bigskip + +I decided to use one of the standard containers for storing the elements of +the matrix and initially went with \texttt{std::vector}. But +since we were going to do arithmentics on the data, and no +requirements were made as to the arithmentics actually being those of +a real matrix, I figure why not use \texttt{std::valarray} and +have all arithmentic operations be per-entry, re-using the behaviour +of \texttt{std::valarray} directly. + +During this change I discovered that \texttt{std::valarray} and \texttt{std::vector} ctor args for creating with a size and a default value for all items are reversed, which would not be caught by the compiler with both of them being integral: + \footnotesize\begin{lstlisting}[language=C++] explicit vector( size_type count, const T& value = T(), @@ -22,9 +28,13 @@ valarray( const T& val, std::size_t count ); \end{lstlisting}\normalsize That, I think, is rather unfortunate. -When using a std container, all memory managing is handled by the +When using a standard container, all memory managing is handled by the container, even when throwing an exception inside the constructor, so -no need to do anything clever inside the matrix implementation itself. +tere is no need to do anything clever inside the matrix implementation +itself. Otherwise a allocating base-class could have been made for +memory ownership as seen during one of the presentations. +The implementation for the statically typed \texttt{Imatrix} can be +seen in the \texttt{imatrix.h} file. I first wrote the entire \texttt{Imatrix} class with the values dynamically allocated inside the \texttt{std::valarray} but wanted to @@ -33,34 +43,93 @@ explore having the matrix dimenions available at compile-time so they should be checkable at compile-time). I did this with template size arguments. -This removed a lot of error checks on matrix dimension matching, which -is part of the type itself - nice! +First of all, this removed a lot of error checks on matrix dimension +matching, which is now part of the type itself. +It also removed the need for the two integer members from the first +imeplementation, carrying the matrix dimensions, so the change now +also saved 16 bytes of memory. -It also removed the need for the two integer members carrying the -width and height - we now also saved 16 bytes of memory! +And, best of all, we get a compile error if for example we try +multiplying two matrices where their dimensions doesn't adhere to the +algebraic rules! -... and we get a compile error if for example we try multiplying two -matrices where their dimensions doesn't adhere to the algebraic rules! +For the \texttt{Row()} and \texttt{Column()} I briefly considered +returning a span to be able to return references to a row or column +for dynamic iteration, but decided to stick with +the \texttt{std::vector} as described in the assignment. +The code for the size-templated \texttt{Imatrix} can be seen in +the \texttt{imatrix\_nm.h} file. -Final step is to templatize the underlying typeof the matrix. Done so -by simply replacing int with T and all the places where 0 is assigned -to the value type, replacing it with the default value T{} +\bigskip + +Next step was to templatize the underlying type of the matrix. This +was more or less done by simply replacing \texttt{int} with \texttt{T} +and all the places where 0 is assigned to the value type, replacing it +with the default value \texttt{T\{\}}. +For the \texttt{Move()} function a $0$ assignment was needed and this +I replaced with an empty initializer list, \texttt{\{\}}. +The new templatized class is called \texttt{Matrix} and can be found in +the \texttt{matrix.h} file. When instantiating a template only the required/used functions are generated (or so it appears). -In other words, I can instante a \texttt{Matrix c1} -without getting any compile errors because I don't actually call -anything on it. -If I add a \texttt{c1 + 1} somewhere it will complain about not being -able to add, but not any of the other unused operations. -When adding the requirements of concepts, I will get errors for all -the uses, not just the ones I use. In other words, concepts might end -up requirering more work to be done by a developer than strictly -needed. This I think is actually not that good. -Adding the requirement to each of the member functions might be -better. - - -I actually made some errors in the matrix subscripts in the matrix -multiplication/division code, which was shown to me by an assertion. +In other words; a \texttt{Matrix c1} can be instantiated +without getting any compile errors as long as no algebraic methods are +being called on it. +If the line \texttt{c1 + 1} is added somewhere it will complain about +not being able to add with \texttt{int}, but not any of the other unused +operations. +When adding the requirements of concepts, I expect to get errors for +all the uses, not just the ones used in the program. +In other words, concepts might end up requirering more work to be done +by a developer than strictly needed. This might not always be +desirebale - or at least it is something that should be thought about +when designing a new component. +One option to mimic the same behaviour, but with concepts, could +perhaps be to add requirements to each of the member functions. This +is however not what I did in this exercise. + +I made a concept, \texttt{supports\_modulo}, used for only +``activating'' the modulo operator if the underlying type supports it. + +I made an extensive, templated, test function that exercised all +operator types of any of the matrix types - and here the compiler errors +for \texttt{Chess\_piece} were not very helpful (the same goes +for \texttt{std::string}). This can be seen in the \texttt{matrix.cc} +file. +In the test program, to prevent performing modulo operations on +matrices that doesn't support them, I used +the \texttt{supports\_modulo} concept together with a \texttt{if +constexpr} to only try to call the module operator when enabled for +the class. + +I made another concept, \texttt{supports\_matrix}, used for the +underlying type of the \texttt{Matrix} class itself, requirering +algebraic functionality on the underlying type. + +After adding requirements for all the needed operators, +the \texttt{Chess\_piece} and \texttt{std::string} matrices now failed +as expected but this time with a much shorter and to-the-point error +message telling me exactly what would be needed for them work. + +I added empty dummy operator functions to fulfill the requirements (and +commented out the \texttt{std::string} one) and now the program +compiles and runs as expected. + +In hindsight, using the \texttt{std::valarray} may have resulted in me +writing less code in the \texttt{Matrix} implementation, but because +of implementation details inside \texttt{std::valarray} I may have +ended up having to write more algebraic operators +on \texttt{Chess\_piece} to get it to work. + +\bigskip + +The approach of first writing the statically typed class, then +templatizing it, and finally analyzing the actual requirements of the +underlying type and expressing those as a concept requirements, was a +nice way to ``eat the elephant'', being able to focus on just one +aspect of the code at one time. My guess is also that it would have +been hard to foresee what the actual type requirements would end up being +without having written the code yet. + \end{document} diff --git a/a5/matrix.cc b/a5/matrix.cc index 4a34fa0..b76526c 100644 --- a/a5/matrix.cc +++ b/a5/matrix.cc @@ -3,6 +3,7 @@ #include "matrix.h" #include +#include template std::ostream& operator<<(std::ostream& s, const Matrix& m) @@ -224,4 +225,11 @@ int main() Matrix m2; test(m1, m2); } + + std::cout << "Matrix:\n"; + { +// Matrix m1; +// Matrix m2; +// test(m1, m2); + } } -- cgit v1.2.3