Reputation: 863
I am trying to understand this example from a pluralsight video (that I've modified a little):
#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;
}
My main two questions are why is the partial specialization and is it possible to modify the code so it can take a lambda as well?
Upvotes: 1
Views: 1012
Reputation: 940
The following code will accept both function pointers and lambdas, where the variadic arguments are moved the operator() and the specialization is no longer needed.
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};
}
Upvotes: 2
Reputation: 50550
why is the partial specialization?
The intent is to provide a function type, but also get out of it return type and types of the arguments.
You can do that only with a partial specialization, that is the following one:
template<typename> struct S;
template<typename R, typename... Args>
struct S<R(Args...)> { /* ... */ };
As you can see, the primary template doesn't have a definition, for it's not required at all in this case.
Anyway, note that it accepts only one type. The intent is to use it as it follows: S<void(int, char)>
.
How could you know that the return type is void
and int
and char
are your parameters in the primary template? You cannot, but you do within the specialization.
is it possible to modify the code so it can take a lambda as well?
You can even get rid of std::function
s and function pointers, so as to have a lambda-only based solution. Using lambdas you can easily capture or invoke the other functions.
Here is a minimal, working example:
#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);
}
Upvotes: 3