Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to replace an optional std::function parameter with a template parameter?

Tags:

c++

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 :)

like image 778
noProblem Avatar asked Aug 10 '26 23:08

noProblem


2 Answers

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.
}
like image 164
Jarod42 Avatar answered Aug 12 '26 14:08

Jarod42


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

like image 26
Marek R Avatar answered Aug 12 '26 14:08

Marek R



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!