3

Python hat eine sehr nützliche Funktion von function decorators, die darüber hinaus Zusammensetzung ermöglicht. Schreiben Sie zum Beispiel, wenn eine Funktion foo, dann können Sie feststellen, dass Sie foo mögen memoized, sondern auch retried mehr als nur ein einziges Mal bei einer Cache-Miss in der foo auch eine Ausnahme auslöst, durch:Composable C++ - Funktions-Dekoratoren

@lru_cache 
@retry 
def foo(...): 

Mit der Dekorator-Zusammensetzbarkeit können Funktionen wie foo und einzelne Funktionsdekoratoren unabhängig voneinander entwickelt und dann nach Bedarf gemischt werden. Es wäre schön, wenn wir das auch in C++ tun könnten (soweit möglich).

Zwar gibt es mehrere Fragen zu StackOverflow in Bezug auf Funktion Dekoratoren, aber alle scheinen nicht zusammensetzbare zu generieren, aufgrund der starren Annahmen über die Signatur der verzierten Funktion. Z. B. bedenken Sie die ausgezeichnete Antwort mit der besten Wahl an this question. Die Dekoration ist von der Form

template <typename R, typename... Args> 
std::function<R (Args...)> memo(R (*fn)(Args...)) { 

Folglich ist es cannot be applied to the result of itself (zugegebenermaßen nicht viel von einem Problem für die spezifische Dekorateur Verwendung von memoization).

Wie können wir komponierbare Funktionsdekoratoren dann schreiben?

+0

Das Beispiel, das Sie geben, ist nicht sehr überzeugend; In dieser Implementierung ist nichts enthalten, was einen Funktionszeiger erfordert. –

Antwort

0

Eine Möglichkeit, zusammensetzbare Funktionsdekoratoren zu erstellen, besteht darin, die Annahme der vom Dekorateur vorgenommenen Signatur zu lockern. Sagen wir

template<class Fn> 
struct foo_decorator 
{ 
    template<typename ...Args> 
    auto operator()(Args &&...args) const -> 
     typename std::result_of<Fn(Args...)>::type; 
}; 

template<class Fn> 
foo_decorator<Fn> make_foo(const Fn &fn) {return foo_decorator<Fn>();} 

Wie zu sehen ist, sowohl make_foo und foo_decorator sind parametrisiert durch class Fn haben, die an diesem Punkt praktisch alles sein kann. Daher können sie zum Beispiel sowohl eine Lambda-Funktion als auch einen Funktor verwenden. Die Argumente (und der Rückgabetyp) werden (zur Kompilierzeit) auf den Aufruf zurückgestellt, wobei die abgeleiteten Schablonenparameter eines C++ - Funktionsaufrufs bei Bedarf die restlichen Details ausfüllen.

diese Verwendung, hier eine einfache Protokollierung Dekorateur ist:

#include <type_traits> 
#include <cmath> 
#include <iostream>  

template<class Fn> 
struct logger 
{ 
    logger(const Fn &fn, const std::string &name) : m_fn(fn), m_name{name}{} 

    template<typename ...Args> 
    auto operator()(Args &&...args) const -> 
     typename std::result_of<Fn(Args...)>::type 
    { 
     std::cout << "entering " << m_name << std::endl; 
     const auto ret = m_fn(std::forward<Args>(args)...); 
     std::cout << "leaving " << m_name << std::endl; 
     return ret; 
    } 

private: 
    Fn m_fn; 
    std::string m_name; 
}; 

template<class Fn> 
logger<Fn> make_log(const Fn &fn, const std::string &name) 
{ 
    return logger<Fn>(fn, name); 
} 

int main() 
{ 
    auto fn = make_log([](double x){return std::sin(x);}, "sin"); 
    std::cout << fn(4.0) << std::endl; 
} 

Here ist ein Build dieses Dekorateur, ist here ein Build von einem erneuten Versuch Dekorateur und here ist eine Ansammlung einer Zusammensetzung von ihnen. Ein Nachteil dieses Ansatzes ist für Fälle, in denen der Dekorator einen Zustand hat, der auf der Signatur der Funktion beruht, z. B. der ursprüngliche Fall der Memoisierung. Es ist möglich, dies mit dem Typ Löschen zu behandeln (siehe Build here), aber dies hat eine Reihe von Nachteilen, von denen einer ist, dass Fehler, die konzeptionell bei der Kompilierung abgefangen werden konnten, jetzt zur Laufzeit abgefangen werden (beim Typ löschen) entdeckt illegale Verwendung).

3

Ein anderer Weg zusammensetzbare Funktion Dekorateure erstellen unter Verwendung eines Satzes von mixin Klassen ist.
Es folgt ein minimales, Arbeitsbeispiel:

#include<iostream> 
#include<functional> 
#include<utility> 
#include<type_traits> 

template<class T> 
struct LoggerDecoratorA: public T { 
    template<class U> 
    LoggerDecoratorA(const U &u): T{u} { } 

    template<typename... Args> 
    auto operator()(Args&&... args) const -> 
     typename std::enable_if< 
      not std::is_same< 
       typename std::result_of<T(Args...)>::type, 
       void 
      >::value, 
     typename std::result_of<T(Args...)>::type>::type 
    { 
     using namespace std; 
     cout << "> logger A" << endl; 
     auto ret = T::operator()(std::forward<Args>(args)...); 
     cout << "< logger A" << endl; 
     return ret; 
    } 

    template<typename... Args> 
    auto operator()(Args&&... args) const -> 
     typename std::enable_if< 
      std::is_same< 
       typename std::result_of<T(Args...)>::type, 
       void 
      >::value, 
     typename std::result_of<T(Args...)>::type>::type 
    { 
     using namespace std; 
     cout << "> logger A" << endl; 
     T::operator()(std::forward<Args>(args)...); 
     cout << "< logger A" << endl; 
    } 
}; 

template<class T> 
struct LoggerDecoratorB: public T { 
    template<class U> 
    LoggerDecoratorB(const U &u): T{u} { } 

    template<typename... Args> 
    auto operator()(Args&&... args) const -> 
     typename std::enable_if< 
      not std::is_same< 
       typename std::result_of<T(Args...)>::type, 
       void 
      >::value, 
     typename std::result_of<T(Args...)>::type>::type 
    { 
     using namespace std; 
     cout << "> logger B" << endl; 
     auto ret = T::operator()(std::forward<Args>(args)...); 
     cout << "< logger B" << endl; 
     return ret; 
    } 

    template<typename... Args> 
    auto operator()(Args&&... args) const -> 
     typename std::enable_if< 
      std::is_same< 
       typename std::result_of<T(Args...)>::type, 
       void 
      >::value, 
     typename std::result_of<T(Args...)>::type>::type 
    { 
     using namespace std; 
     cout << "> logger B" << endl; 
     T::operator()(std::forward<Args>(args)...); 
     cout << "< logger B" << endl; 
    } 
}; 

int main() { 
    std::function<int()> fn = [](){ 
     using namespace std; 
     cout << 42 << endl; 
     return 42; 
    }; 

    std::function<void()> vFn = [](){ 
     using namespace std; 
     cout << "void" << endl; 
    }; 

    using namespace std; 

    decltype(fn) aFn = 
     LoggerDecoratorA<decltype(fn)>(fn); 
    aFn(); 

    cout << "---" << endl; 

    decltype(vFn) bVFn = 
     LoggerDecoratorB<decltype(vFn)>(vFn); 
    bVFn(); 

    cout << "---" << endl; 

    decltype(fn) abFn = 
     LoggerDecoratorA<LoggerDecoratorB<decltype(fn)>>(fn); 
    abFn(); 

    cout << "---" << endl; 

    decltype(fn) baFn = 
     LoggerDecoratorB<LoggerDecoratorA<decltype(fn)>>(fn); 
    baFn(); 
} 

Ich bin nicht sicher, was der Probleme, die Sie erwähnen, es löst tatsächlich, aber das Gefühl für Änderungen frei zu fragen, und ich werde versuchen, es wenn möglich zu aktualisieren.

+0

Danke für die Antwort! –

+0

Gern geschehen, ich hoffe, dass es Ihren Erwartungen entspricht. Es funktioniert nur gut mit Funktionen, aber es war, was Sie gesucht haben, nicht wahr? – skypjack

+0

@AmyTavory aktualisiert mit ein bisschen sfinae – skypjack