I have a function like this:
void WriteLog(int severity, ...);
And it is used in a next way:
WriteLog(2, "%d\n", SomeHugeCalculations());
But in case the application is configured to write logs only with severity > 2
, execution of SomeHugeCalculations()
is redundant.
I think it can be solved by wrapping it into a macro like this:
WRITE_LOG(severity, ...) if(severity <= threshold) WriteLog(severity, __VA_ARGS__)
But... Are there other(modern?) approaches to solve the issue?
CodePudding user response:
There is this approach :
#include <chrono>
#include <iostream>
#include <thread>
// log accepts parameterless functions, and lambdas
template<typename Fn>
void log(const int severity, const std::string& message, Fn fn)
{
if (severity > 2)
{
// only execute function when really logging
fn();
std::cout << message << "\n";
}
}
void lengthy_stuff()
{
std::this_thread::sleep_for(std::chrono::seconds(2));
}
int main()
{
log(1, "Hello", lengthy_stuff);
log(3, "World!", lengthy_stuff);
}
CodePudding user response:
Function parameters are evaluated before the function is called, there is no way around this. When using macros is not an option, you can encode the lazy evaluation in the return type of SomeHugeCalculations()
, e.g. it could return a simple struct
that holds a reference to the data necessary to do the computation and has an eval()
member function that can be called when needed.
This comes with a drawback: the function accepting this new return type must deal with this. It will probably be a variadic template, but how does it know which types want a call to .eval()
and which don't? You could do some template trickery (e.g. "detection idiom") or concept checking (C 20) to always use .eval()
if it exist. That might not be enough though, as there might be types that have an eval()
function but they already represent the object of interest. In this case, some template specializations might help.
Considering the complexity that does into such a solution, I'd suggest to use an established logging library instead.
CodePudding user response:
The approach with macro can be ported C but problematic in form you did it. Consider this code:
if ( logging )
WRITE_LOG(1, "Starting program");
else
printf("Hello world!");
Non-isolated if-statement in macro causes "Hello world!" being printed if logging is true and if 1 is less than threshold because else
is actually related to the hidden if-statement.
With C inlined\templated code is way to go. Much better if you would design some kind of centralized subsystem for the logger.
PS. If C compatibility required, macro wrappers would look something similar to following:
#define WRITENGINE /** here goes a variadic interface to logger */
// this is one of ways to isolate code within
#define STATEMENT(...) do { __VA_ARGS__ } while(0)
#define WRITEF(sev, fmt, ...) STATEMENT( if(sev > threshold) WRITENGINE (fmt, __VA_ARGS__); )
do { __VA_ARGS__ } while(0)
would behave like a singular statement and will be optimized out while preventing compiler to generate warning about extra ; or causing issues with contained code or enclosing flow-control statements. It may wrap up several statements.