I am currently developing a small and simple logging library that acts as a Facade for the Boost.Log v2 C++ library.
My library is almost finished and I have successfully encapsulated Boost.Log, i.e.:
- the API of my library is free of Boost.Log types, i.e. users do only have to deal with my types
- the ABI of my library is free of Boost.Log types, i.e. dependencies do not have to link against Boost.Log.
There is one last thing I am not able to solve at the moment: Lazy parameter evaluation
I'll give a short usage example how Boost.Log behaves:
#include <chrono>
#include <thread>
#include <boost/log/core.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/trivial.hpp>
std::string time_costly_function(
const std::chrono::seconds seconds = std::chrono::seconds{1}) {
std::this_thread::sleep_for(seconds);
return "DONE with time_costly_function";
}
int main() {
boost::log::core::get()->set_filter(boost::log::trivial::severity >=
boost::log::trivial::warning);
BOOST_LOG_TRIVIAL(warning) << "This is evaluated: " << time_costly_function();
BOOST_LOG_TRIVIAL(info) << "This is NOT evaluated: "
<< time_costly_function();
}
If you run the example above you can see that only the arguments for the first BOOST_LOG_TRIVIAL
call are evaluated, i.e. time_costly_function()
is only called once in total.
That is exactly the behavior that I want in my library, but as I mentioned I do not want that users have to deal with Boost.Log directly, but with my small Facade.
The following code illustrates the problem (very simplified to tackle down the actual problem):
#include <boost/log/core.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/trivial.hpp>
#include <chrono>
#include <thread>
std::string time_costly_function(
const std::chrono::seconds seconds = std::chrono::seconds{1}) {
std::this_thread::sleep_for(seconds);
return "DONE with time_costly_function";
}
// Encapsulates Boost.Log. The API of my logging library does not know anything
// about Boost.Log (e.g. the Pimpl idiom is used).
//
// In reality this is a member function of a class in my library with the
// following signature:
//
// void log(const SeverityLevel level, const std::string& message) override;
//
void log(const std::string& message) { BOOST_LOG_TRIVIAL(warning) << message; }
// A custom logging macro.
//
// In reality this is a macro taking the object of a class with the member
// function illustrated above:
//
// FW_LOG(logger, level, message)
//
#define FW_LOG(message) log(message)
int main() {
// TODO(wolters): This does not work, since everything is evaluated outside of
// the Boost.Log macro(s). I want to call my macro as follows.
// FW_LOG(logger, level) << message << time_costly_function();
// I.e. it should work like the Boost.Log macro(s) illustrated above.
FW_LOG("foo" + time_costly_function());
}
I identified the following problems in my code:
- The function
log
may not take astd::string
reference, since that will always lead to an evaluation outside of that function. - The macro
FW_LOG
has to be rewritten in a manner that the following syntax is supported:FW_LOG(logger, level) << message << time_costly_function();
I've thought about using streams and/or perfect forwarding using template functions, but I was unable to come up with a working solution (yet).
Requirements: The code has to be compilable using MSVC 12.0, Boost.Log 1.59.0. C+11 is allowed.
So my actual question is:
How do I have to rewrite the code in the second example (using both the macro and the function) to get the same behavior as in the first example?