I have a function that takes an optional std::function and SonarQube suggests to replace it with a template parameter, mainly because of the run-time cost of std::function.
This is what I currently have using std::function:
void process(Stuff& stuff, std::function<void(std::string_view)> logger = {})
{
// do stuff...
if (logger)
logger("step 1");
// continue with other stuff...
if (logger)
logger("step 2");
// etc.
}
At the call site I could do something like
process(stuff1, [](std::string_view str){
std::cout << "Message: " << str << "\n";
});
or if I'm not interested in logging I could simply call process(stuff2);
I don't have too much experience with templates. What I came up with is the following:
template <typename Logger>
void process(Stuff& stuff, Logger logger)
{
// do stuff
logger("step 1");
// etc.
}
void process(Stuff& stuff)
{
// I don't want to duplicate the implementation
// therefore I delegate to the 2-argument process function.
process(stuff, [](std::string_view){});
}
Does this make sense? It doesn't look very elegant to me. It would be nice to check at compile time whether the logger is needed or not, and based on that the calls to the logging function could be excluded from the compilation (something with if constexpr maybe?).
I'm sorry but my experience with templates is still very limited.
EDIT:
I accepted Jarod42's answer because he provided two ways of using template parameters instead of std::function. I should have been clearer about the whole thing. I wanted to know how to approach the problem using templates simply because I don't have that much experience and I would love to know more about them. I wasn't planning on blindly following SonarQube's advice and neither of changing my company's logging infrastructure.
Thank you all for your answers, I learned a lot today :)
Your way works.
Alternatively, you might provide an empty logger:
struct EmptyLogger
{
void operator()(std::string_view) const { /*Empty*/ }
};
template <typename Logger = EmptyLogger>
void process(Stuff& stuff, Logger logger = {})
{
// do stuff
logger("step 1");
// etc.
}
or use variadic template
template <typename... Loggers>
void process(Stuff& stuff, Loggers... loggers)
{
// do stuff
(loggers("step 1"), ...); // expand to nothing when pack is empty,
// even in non-optimized build.
// etc.
}
Sometimes instead using default value it is better to use overload. If you are using C++20 you can also use auto to have cleaner version of template:
void process(Stuff&, auto logger)
{
// do stuff
logger("step 1");
// etc.
}
void process(Stuff& stuff)
{
process(stuff, [](std::string_view) { });
}
https://godbolt.org/z/9jarbWPvo
Yous can use also concepts:
template<typename F>
concept LogsStringView = requires (F f) {
f(std::string_view{});
};
void process(Stuff&, LogsStringView auto logger)
{
// do stuff
logger("step 1");
// etc.
}
void process(Stuff& stuff)
{
process(stuff, [](std::string_view) { });
}
https://godbolt.org/z/sjhb7YdGY
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With