问题
I'm implementing some data structures, each supporting a set of commands such as INSERT value
.
I've used a tokenizer to generate a vector that holds each word/value.
I want to be able to output to a .txt file the time every function call took, plus what the function returned if it does return something.
For example, if the command is INSERT AVLTREE 4
, I want to just output the time calling avl.insert(4)
took.
If the command is SEARCH AVLTREE 4
, I want to output the time calling avl.search(4)
took, as well as its result (e.g. "SUCCESS"
or "FAILURE"
).
There are probably many things wrong with the following code, but here's what I came up with:
I made two files (.cpp/.hpp) which contain the following self-hacked function wrapper, and also a variant as well as a struct:
// WRAPPER CPP
// file: wrap.cpp
#include "wrap.hpp"
#include <chrono>
#include <string>
#include <utility>
#include <boost/variant.hpp>
using std::chrono::high_resolution_clock;
using std::chrono::time_point;
using std::chrono::nanoseconds;
using std::string;
using std::to_string;
using std::forward;
using boost::get;
using boost::static_visitor;
using boost::apply_visitor;
// I'm overloading std::to_string, so it works on std::strings as well.
string to_string(const string &value)
{
return value;
}
// I want to apply to_string on whatever is inside my variant.
class to_string_visitor : public static_visitor<>
{
public:
template <typename T>
void operator()(T & operand) const
{
to_string(operand);
}
};
// Takes two points in time and returns the time
// between them in nanoseconds.
const nanoseconds::rep duration(const nanoseconds tpoints_difference) noexcept
{
const auto result = tpoints_difference.count();
return result;
}
// Generates a point in time.
const high_resolution_clock::time_point timeNow(void) noexcept
{
const auto result = high_resolution_clock::now();
return result;
}
// Here's where's the problematic magic happens:
// The ret boolean is set to true if the function F returns a value,
// otherwise, it is set to false.
//
// Variadic arguments are being taken and then std::forwarded to F.
template<typename F, typename... Args>
const output wrapper(bool ret, F function, Args&&... args) noexcept
{
// Generate a point in time, t1.
const high_resolution_clock::time_point t1 = timeNow();
// If F returns a result,
if (ret == true)
{
// assign it to result (my variant).
result = function(forward<Args>(args)...);
}
else
{
// just call F with Args forwarded.
function(forward<Args>(args)...);
}
// Generate another point in time, t2 and
// count the difference between t2 - t1.
const auto elapsed = duration(timeNow() - t1);
// Make whatever is inside result a string
// using std::to_string.
apply_visitor(to_string_visitor(), result);
// My struct
output out;
// which contains the time elapsed and
// the result returned
out.time = elapsed;
out.result = get<string>(result);
// I can theoretically use both time elapsed and
// result returned however I want. Hooray!..almost:(
return out;
}
Here is the variant result
:
// These are all the types a data structure function may return.
variant<int, unsigned, uint32_t, size_t, graph_size, string> result = 0;
graph_size
, just for reference:
struct graph_size
{
unsigned vertices; //Number of vertices that the Graph currently contains
unsigned edges; //Number of edges that the Graph currently contains
};
And, finally, the output
struct:
typedef struct output
{
double time; // function call time
string result; // what function returned
// notice that if function returned nothing,
// result will be an empty string.
output() : time(0), result("") {}
} output;
I'm trying to use wrapper
like so:
AVL avl;
// stuff
auto out = wrapper(true, avl.insert, 4);
I get the following error:
invalid use of non-static member function 'void AVL::insert(int)'
And here's a bonus one which should hint me towards what I botched but just can't quite grasp it:
no matching function for call to 'wrapper(bool, <unresolved overloaded function type>, unsigned int&)'
Any thoughts?
I appreciate all time spent in advance :)
EDIT 1: The question title may not be very suitable, I'll merrily change if you have something good in mind
回答1:
As you need different behaviour depending of return type, you might use specialization/SFINAE, something like:
template<typename F, typename... Args>
auto wrapper(F function, Args&&... args) noexcept
-> std::enable_if_t<std::is_same<void, std::invoke_result_t<F, Args&&...>>::value, output>
{
const high_resolution_clock::time_point t1 = timeNow();
function(forward<Args>(args)...);
const auto elapsed = duration(timeNow() - t1);
output out;
out.time = elapsed;
out.result = ""; // void return
return out;
}
template<typename F, typename... Args>
auto wrapper(F function, Args&&... args) noexcept
-> std::enable_if_t<!std::is_same<void, std::invoke_result_t<F, Args&&...>>::value, output>
{
const high_resolution_clock::time_point t1 = timeNow();
auto result = function(forward<Args>(args)...);
const auto elapsed = duration(timeNow() - t1);
output out;
out.time = elapsed;
out.result = to_string(result);
return out;
}
with possible usage:
AVL avl;
auto out = wrapper([&](){ return avl.insert(4);});
回答2:
The problem lies in a corner case of your wrapper()
function usage. It's quite tricky to make it work with member functions and possibly with void
-returning functions (see Jarod42's excellent answer on that). This:
wrapper(true, avl.insert, 4);
will not compile for several reasons. Not only avl.insert
is invalid, but even if you tried to pass a member function by a pointer and pass an object to call it, like so:
wrapper(true, &AVL::insert, avl, 4);
it still won't compile, but now due to how you invoke it inside wrapper
.
The workaround is to use a suitable tool for this kind of job - std::invoke. It correctly handles free functions, member functions etc. Example usage:
#include <iostream>
#include <functional>
struct foo {
void bar(int x) {
std::cout << x;
}
};
template <typename F, typename... Args>
void wrapper(F f, Args&&... args) {
std::invoke(f, std::forward<Args>(args)...);
}
void free_bar() {
std::cout << "free";
}
int main() {
foo f;
wrapper(&foo::bar, f, 1); // pass a pointer to member function and the instance itself, then arguments
wrapper(free_bar);
}
Notice how the code correctly handles each case. std::invoke
just does the right thing.
来源:https://stackoverflow.com/questions/56685232/problems-trying-to-construct-a-wrapper-measuring-function-call-time