Search code examples
c++templateswrappervariantvariadic

Problems trying to construct a wrapper measuring function call time


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


Solution

  • 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);});