2017-01-15 3 views
1

Ich versuche, dieses Beispiel aus einem Plural Video zu verstehen (das ich ein wenig geändert haben):Decorator-Muster in C++ mit Vorlagen

#include <iostream> 
#include <functional> 

//this next line is partial specialization, but I don't understand why it's necessary 
template<typename> struct Logger; 

template <typename R, typename... Args> 
struct Logger<R(Args...)> 
{ 
    std::function<R(Args...)> func_; 
    std::string name_; 

    Logger(const std::function<R(Args...)>& func, const std::string& name): 
     func_{func}, name_{name} {} 

    R operator() (Args... args) 
    { 
     std::cout << "Entering " << name_ << std::endl; 
     R result = func_(args...); 
     std::cout << "Exiting " << name_ << std::endl; 
     return result; 
    } 
}; 

// this is how the example was originally written. It uses a function pointer as an argument which is ok except you can't give it a lambda. 
template <typename R, typename... Args> 
auto make_logger(R (*func)(Args...), const std::string& name) ->Logger<R(Args...)> 
{ 
    return Logger<R(Args...)>{std::function<R(Args...)>(func), name}; 
} 

//this is my attempt to make it so it will accept a lambda but it doesn't work. 
template <typename R, typename... Args> 
auto make_logger2(std::function<R(Args...)> func, const std::string& name) ->Logger<R(Args...)> 
{ 
    return Logger<R(Args...)>{std::function<R(Args...)>(func), name}; 
} 

double add(double a, double b, double c) 
{ 
    std::cout << a << " + " << b << " + " << c << " = " << a + b + c << std::endl; 

    return a + b + c; 
} 

int main() 
{ 

    auto logged_add = make_logger(add,"Add"); 
    auto result = logged_add(2,3,5); 

// auto lm = [](std::string str1, std::string str2){return str1 + str2;}; 
// auto logged_string = make_logger2(lm, "string add"); 
// auto result2 = logged_string("Hello ", "World!"); 

    //I get the following compile error if the next two lines are uncommented: 
    // main.cpp:101: error: no matching function for call to 'make_logger2(main()::<lambda(std::__cxx11::string, std::__cxx11::string)>, const char [11])' 

    //auto logged_string = make_logger2([](std::string str1, std::string str2){return str1 + str2;},"string add"); 
    //auto result2 = logged_string("Hello ", "World!"); 

    std::cout << "result = " << result << std::endl; 
    //std::cout << "result2 = " << result2 << std::endl; 

    return 0; 
} 

Meine beiden wichtigsten Fragen, warum ist die partielle Spezialisierung und ist es möglich, den Code zu ändern, so dass es auch ein Lambda nehmen kann?

Antwort

3

warum ist die partielle Spezialisierung?

Die Absicht ist, einen Funktionstyp zur Verfügung zu stellen, aber auch Rückgabetyp und Typen der Argumente herauszuholen.
können Sie tun, dass nur mit einer teilweisen Spezialisierung, das ist die folgende:

template<typename> struct S; 

template<typename R, typename... Args> 
struct S<R(Args...)> { /* ... */ }; 

Wie Sie sehen können, ist die primäre Vorlage keine Definition haben, denn es ist in diesem Fall überhaupt erforderlich ist nicht.
Wie auch immer, beachten Sie, dass es nur einen Typ akzeptiert. Die Absicht ist, es wie folgt zu verwenden: S<void(int, char)>.
Wie können Sie wissen, dass der Rückgabetyp void und int und char Ihre Parameter in der primären Vorlage sind? Sie können nicht, aber Sie tun innerhalb der Spezialisierung.

Ist es möglich, den Code so zu ändern, dass er auch ein Lambda braucht?

Sie können sogar std::function s und Funktionszeiger loswerden, um eine nur Lambda-basierte Lösung zu haben. Mit Lambda können Sie die anderen Funktionen einfach erfassen oder aufrufen.

Hier ist ein minimal, funktionierendes Beispiel:

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

template <typename F> 
struct Logger: F 
{ 
    std::string name_; 

    Logger(F &&func, const std::string& name): 
     F{std::move(func)}, name_{name} {} 

    template<typename... Args> 
    auto operator() (Args&&... args) 
    { 
     std::cout << "Entering " << name_ << std::endl; 
     auto result = F::operator()(std::forward<Args>(args)...); 
     std::cout << "Exiting " << name_ << std::endl; 
     return result; 
    } 
}; 

template <typename F> 
auto make_logger(F &&f, const std::string& name) 
{ 
    return Logger<std::decay_t<F>>{std::move(f), name}; 
} 

double add(double a, double b, double c) 
{ 
    std::cout << a << " + " << b << " + " << c << " = " << a + b + c << std::endl; 
    return a + b + c; 
} 

int main() 
{ 
    auto logged_add = make_logger([](auto... args) { return add(args...); }, "Add"); 
    auto result = logged_add(2,3,5); 
} 
2

Der folgende Code akzeptiert sowohl Funktionszeiger als auch Lambdas, wobei die Variadic-Argumente durch den Operator() verschoben werden und die Spezialisierung nicht mehr benötigt wird.

template <typename Func> 
struct Logger 
{ 
    Func func_; 
    std::string name_; 

    Logger(Func&& func, const std::string& name): 
     func_{std::move(func)}, name_{name} {} 

    template<typename... Args> 
    decltype(auto) operator() (Args&&... args) 
    { 
     std::cout << "Entering " << name_ << std::endl; 
     decltype(auto) result = func_(std::forward<Args>(args)...); 
     std::cout << "Exiting " << name_ << std::endl; 
     return result; 
    } 
}; 

template <typename Func> 
decltype(auto) make_logger(Func&& func, const std::string& name) 
{ 
    return Logger<std::decay_t<Func>>{std::move(func), name}; 
} 

live demo

+1

Sie werden zu 'std :: move' dass' Func' in den Speicher in Ihrem Make-Funktion wollen. Für industrielle Qualität würde ich auch einen konvertierenden ctor zu Logger schreiben (wo, wenn "FuncA" in "FuncB" umwandelbar ist, so ist "Logger ' zu 'Logger '). Außerdem haben make und Logger ctor den String by-value und 'std :: move', um Zuweisungen zu sparen. – Yakk

+1

'decltype (auto) operator()' und 'declltype (auto) result' werden Unterstützung für die Rückgabe durch Referenz hinzufügen. – Potatoswatter

+0

oh ich wusste nicht über auto/decltype (auto) diff, das ist ein gutes wissen :) –