0

There are somecases I have to add statement, like logs or trace to an expression, for example (I wanna log exactly the time for prep_stmt.executeQuery() and still I need its return value):

zdb::PreparedStatement prep_stmt = conn.prepareStatement(sql.data());
prep_stmt.setLLong(1, key);
zdb::ResultSet result = prep_stmt.executeQuery();

// do some stuff with the result

if (result.next()) {
    SCOPE_LOG(HANDLE_RESULTS);
    auto [data, size] = result.getBlob("v");
    // value is a protobuff message
    value->ParseFromArray(image, size);
}

Say I wanna log the time ellasped in prep_stmt.executeQuery(). So I add some macros like these:

// only for debug
#define EXPR_LOG_BEGIN(Name) \
    [&]() { \
        SCOPE_LOG(Name); \
        return
#define EXPR_LOG_END }();

here SCOPE_LOG is a macro for logging using raii (folly's SCOPE_EXIT macro and spdlog):

#define SCOPE_LOG(Expr) \
    auto start =  chrono::system_clock::now(); \
    SCOPE_EXIT {        \
        auto end = std::chrono::system_clock::now(); \
        std::chrono::duration<double, std::milli> elapsed_milliseconds = end - start; \
        SPDLOG_LOGGER_INFO(::Logger::getLogger(),  \
            "[[title={},elapsed_ms={}]]",                  \
            #Expr, elapsed_milliseconds.count()); \
    }

And change the above code to:

zdb::PreparedStatement prep_stmt = conn.prepareStatement(sql.data());
prep_stmt.setLLong(1, key);
zdb::ResultSet result =
        EXPR_LOG_BEGIN(DB_Execute)
        prep_stmt.executeQuery();
        EXPR_LOG_END

So the code is expanded to:

zdb::PreparedStatement prep_stmt = conn.prepareStatement(sql.data());
prep_stmt.setLLong(1, key);
zdb::ResultSet result = [&]() {
    SCOPE_LOG(Name);
    return prep_stmt.executeQuery();
}();

Is it safe to du this stuff? will it be any hidden bug for using an immediate lambda for adding statement for an expression? Is there any better way to do this? I found a similar question: How to immediately invoke a C++ lambda? but it does not state its usecases explicitly

Sneftel
  • 40,271
  • 12
  • 71
  • 104
onriv
  • 145
  • 1
  • 7
  • what is the lambda good for? WHy not simply `zdb::ResultSet result = prep_stmt.executeQuery(); SCOPE_LOG(Name);` or perhaps `zdb::ResultSet result = prep_stmt.executeQuery(); { SCOPE_LOG(Name); }` ? What is the actual problem you are trying to solve? – 463035818_is_not_an_ai Sep 17 '20 at 11:59
  • @idclev463035818 I wanna log the ellasped time for running `prep_stmt.executeQuery()`. there are some other statemements in current scope. SCOPE_LOG define an object that will print a log in its destructor – onriv Sep 17 '20 at 12:02
  • The lambda(temporary) is destroyed right afer its executed via (). So, regarding its scope (it doesnt outlive any catched variables) it should be safe. – StPiere Sep 17 '20 at 12:05

1 Answers1

2

Maybe better to write a templated wrapper like this:

void log(const std::string& str)
{
  std::cout << str << std::endl;
}

template <class Functor>
typename std::result_of<Functor()>::type my_wrapper(Functor f)
{
  // do whatever you need inside
  auto start = std::chrono::system_clock::now();
  auto result = f();
  auto end = std::chrono::system_clock::now();
  std::chrono::duration<double, std::milli> elapsed_milliseconds = end - start;
  log("Elapsed time: " + std::to_string(elapsed_milliseconds.count()));
  return result;
}

int main()
{
  std::cout << my_wrapper([]()
    {
      std::this_thread::sleep_for(std::chrono::seconds(1));
      return 3.4;
    }) << std::endl;
  return 0;
}

It is a bit more comfortable than macro, because you don't need to worry about parentheses etc.

Since c++14 you can simplify the signature:

template <class Functor>
auto my_wrapper(Functor f)

In c++20 you can add a constraint

template <class Functor>
auto my_wrapper(Functor f) requires std::invocable<Functor>

to have better compiler diagnostics in case you pass some function that needs arguments.

pptaszni
  • 5,591
  • 5
  • 27
  • 43