[dune-functions] Fwd: Paper: The interface for functions in the dune-functions module

Oliver Sander oliver.sander at tu-dresden.de
Mon Feb 29 21:03:49 CET 2016


Hi guys,

today I received this email below from a colleague who happened to find our
paper on dune-functions (he's in cc).  His comments are insightful, and you
may find them interesting.

Best,
Oliver


-------- Forwarded Message --------
Subject: Paper: The interface for functions in the dune-functions module
Date: Mon, 29 Feb 2016 14:17:15 +0000
From: Simon Praetorius <Simon.Praetorius at tu-dresden.de>
To: oliver.sander at tu-dresden.de


Hallo Oliver,

ich bin kürzlich über euren neuen preprint (siehe Betreff) gestoßen.
Wusste gar nicht, dass man ein solches technisches Paper zur
Implementierung eines Features irgendwo veröffentlichen kann :-)
Prinzipiell finde ich den Ansatz sehr schön und es gibt wieder
Analogien zu AMDiS. Dort gab es schon immer ein Interface für
Funktionen das auf virtual inheritance basiert (Bei uns
"AbstractFunction" genannt) und erst seit den neusten Entwicklungen
haben wir auf Funktoren ohne "virtual" angefangen manches umzustellen.
Dabei gibt es natürlich das Problem, dass man die Funktion manchmal
gerne in einer Variable ablegen möchte. Der Ansatz über std::function
war dann auch meine Idee und wurde z.T. auch implementiert. Für die
Auswertung einer Funktion auf Elementebene gibt es entsprechend eine
andere Schnittstelle. Das ist bei uns nicht vereint in eine gemeinsame
Struktur.

Ein paar kleine Hinweise/Anmerkungen/Fragen hätte ich allerdings noch:
- Der Ansatz die Konzepte eines Templates mittels static_assert zu
überprüfen sind recht angenehm. Man kann explizit eine Fehlermeldung
formulieren. Allerdings ändert dies quasi nichts an der Signatur der
Funktion, sondern erzeugt eine Fehlermeldung erst bei Instantiierung
des Funktionsrumpfes. Das führt dazu, dass man in der Regel noch viele
Weitere Fehlermeldungen bekommt, die auf dem nicht erfüllten Konzept
des Funktionsarguments folgen. Z.B.

1: template <class F, class T>
2: double call(F&& f, T&& x)
3: {
4:   static_assert( concept::Function<F, double(double)>::value, "Fehler" );
5:   return f(std::forward<T>(x));
6: }

erzeugt zuerst die gewollte Fehlermeldung "Fehler" und danach einen
weitere Fehler, dass in Zeile 5 das Objekt f nicht als Funktion
verwendet werden kann. Das ist in diesem Fall sogar die richtige
Fehlermeldung, nur wollte man sie eigentlich durch das static_assert
vorher abfangen. Das bedeutet: Bei komplizierten Funktionen kommen
möglicherweise sehr viele Fehlermeldungen und irgendwo dazwischen ist
dann das static_assert und man muss suchen was eigentlich der Auslöser
für den Fehler war.

Ich persönlich finde es schicker wenn die Konzept-Überprüfung bereits
bei der Instantiierung der Templates geschieht (und damit
gewissermaßen in die Signatur der Funktion mit eingeht). D.h. man
würde etwas früher abbrechen und der komplette Inhalt der Funtion wäre
noch gar nicht relevant und würde keine weiteren Fehler provozieren:

1: template <class F, class T, class =
2:   requires<concept::Function<F, double(double)>> >
3: double call(F&& f, T&& x)
4: {
5:   return f(std::forward<T>(x));
6: }

Wobei 'requires' hier ein einfaches Alias für enable_if ist:

1: template <class C, class T = void>
2: using requires = typename std::enable_if< C::value, T>::type;

Dies produziert leider nicht immer so aussagekräftige Fehlermeldungen
wie ein static_assert. Mit ein paar "Tricks" kann man das aber
trotzdem produzieren mit einer kleinen Abwandlung und einem Macro als
Unterstützung:

0: #define ERROR(...) bool
1: template <class C, class T = bool>
2: using requires = typename std::enable_if< C::value, T>::type;
3: template <class F, class T,
4:   requires< concept::Function<F, double(double)> > = ERROR( Fehler ) >
5: double call(F&& f, T&& x) { ... }

Zumindest im gcc Wird dann der Fehlertext mit ausgegeben in der Fehlermeldung.


- Bei der Implementierung von dune_function (als Erweiterung von
std::function) nutzt ihr einen Konstruktor mit universal-referenz als
Argument:

1: template < class Range , class Domain >
2: struct dune_function < Range ( Domain ) >
3: {
4:   template < class F >
5:   dune_function ( F && f ) :
6:     f_ ( new FunctionWrapper < Range < Domain > , F >( f ) )
7:   {}
8: // ...
9: }

Möglicherweise sind nicht alle Details im Paper erwähnt und
ausgeführt. Wenn ihr in FunctionWrapper sowieso nur Funktionen als
const& akzeptiert, macht es eventuell keinen Sinn eine
universal-reference im dune_function Konstruktor zu akzeptieren. Dies
führt nur zu weiteren Komplikationen: Damit wird der klassische
Copy-Konstruktor und Move-Konstruktor verdeckt. Man müsste (wieder
mittels einer Art Konzepte) zusätzlich noch ausschließen dass 'F' vom
Typ 'dune_function' ist, z.B.

4:   template < class F, class =
5:     requires<!is_same< std::decay_t<F>, dune_function >> >
6:   dune_function ( F && f )

Außerdem würde ich dann auch entsprechend 'FunctionWrapper < Range <
Domain > , F >( f )' ersetzen durch

1: FunctionWrapper < Range < Domain > , std::decay_t<F> >(
std::forward<F>(f) )

um mögliche Komplikationen bei der automatische Type-Ableitung zu vermeiden.


Viele Grüße,
Simon





-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 473 bytes
Desc: OpenPGP digital signature
URL: <https://lists.dune-project.org/pipermail/dune-functions/attachments/20160229/057af2ee/attachment.sig>


More information about the dune-functions mailing list