Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass any function by parameter [duplicate]

Explication

Hello, I want to create a function that can execute any type of function, indicating at the end of its execution the time it took. The called function can have a return value or not and 0 or more parameters of any type.

The calling function must print something like this:

Running "myFunction" .....  
Done ! (5210ms)

Basically I want to create a function that calls any type of function passed as a parameter by adding code before and after the call.

What I have done

For now I do it like this.

The calling function:

template <typename T>
T callFunctionPrintTime(std::string fnName, std::function<T()> fn) {
    std::cout << ">> Running " << fnName << " ... " << std::endl;
    auto t1 = std::chrono::high_resolution_clock::now();

    //Call to the target function
    T retVal = fn();

    auto t2 = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();

    std::cout << "Done ! (" << duration << " ms)" << std::endl;

    return retVal;
}

The main

int main()
{
    //Store the function to call
    std::function<unsigned long()> fn = []() {
        return myFunction(15, 10000);
    };

    //Use of the function we are interested in
    auto i = callFunctionPrintTime("myFunction", fn);

    //The return value of myFunction can be used in the rest of the program.
    std::cout << "i: " << i << std::endl;
}

myFunction
This function doesn't matter, it can be anything.

Here we execute a while loop for a given maximum time or maximum number of loop and retrieve the number of loop performed.

unsigned long myFunction(long maxMs, unsigned long maxI) {
    unsigned long i = 0;
    auto tStart = std::chrono::high_resolution_clock::now();
    while (maxMs > (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - tStart).count()) &&
        maxI > i) {
        i++;
    }
    return i;
}

Question

What is the best way for you to do this? I am not satisfied with my code.
I'm not sure I'm using the right way to pass a function of any kind by parameter.
Moreover by using a lambda expression to store my function I can't retrieve the name of the called function. So I have to pass its name by parameter.

like image 579
Maxime Charrière Avatar asked Jan 20 '21 14:01

Maxime Charrière


Video Answer


2 Answers

I'm pretty sure there's no single answer to what's best - but this is a small improvement i.m.o. since it's a bit more generic.

#include <chrono>
#include <iostream>
#include <string>
#include <type_traits>

// enable it for invocables with any type of arguments
template <class Func, class... Args,
          std::enable_if_t<std::is_invocable_v<Func, Args...>, int> = 0>
decltype(auto) callFunctionPrintTime(std::string fnName, Func fn, Args&&... args)
{
    std::cout << ">> Running " << fnName << " ... " << std::endl;
    auto t1 = std::chrono::high_resolution_clock::now();

    //Call to the target function by forwarding the arguments to it
    decltype(auto) retVal = fn(std::forward<Args>(args)...);

    auto t2 = std::chrono::high_resolution_clock::now();
    auto duration = 
        std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();

    std::cout << "Done ! (" << duration << " ms)" << std::endl;

    return retVal;
}

Alternatively, if you don't plan on making overloads for non-invocables (which seems pretty obvious that you wont when I think about it) you can use static_assert instead of SFINAE:

template <class Func, class... Args>
decltype(auto) callFunctionPrintTime(std::string fnName, Func fn, Args&&... args)
{
    static_assert(std::is_invocable_v<Func, Args...>, "must be invocable");
    //...

Test usage:

int& a_func(int i) {
    static int rv = 0;
    rv += i;
    return rv;
}


int main() {
    int& ref = callFunctionPrintTime("a_func 1", a_func, 10);
    
    std::cout << ref << '\n';  // prints 10
    
    ref += 20;

    callFunctionPrintTime("a_func 2", a_func, 100);

    std::cout << ref << '\n';  // prints 130 (10 + 20 + 100)
}

Or some of the alternatives for calling myFunction:

std::function<unsigned long()> fn = []() { return myFunction(15, 100000); };

std::cout << callFunctionPrintTime("myFunction", fn);
std::cout << callFunctionPrintTime("myFunction",
                                   []() { return myFunction(15, 100000); });
std::cout << callFunctionPrintTime("myFunction", myFunction, 15, 100000);

Some useful links: decltype(auto), std::enable_if_t, std::is_invocable_v, SFINAE

like image 186
Ted Lyngmo Avatar answered Sep 20 '22 00:09

Ted Lyngmo


Main idea is correct. there are some details which might be improved:

template <typename Func, typename ... Ts>
decltype(auto) callFunctionPrintTime(std::string_view fnName, Func&& f, Ts&&... args) {
    static_assert(std::is_invocable_v<Func&&, Ts&&...>); // Possibly SFINAE instead.
    std::cout << ">> Running " << fnName << " ... " << std::endl;

    struct Finally {
        std::chrono::time_point<std::chrono::high_resolution_clock> t1 =
            std::chrono::high_resolution_clock::now();

        ~Finally() {
            auto t2 = std::chrono::high_resolution_clock::now();
            auto duration =
                std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();

            std::cout << "Done ! (" << duration << " ms)" << std::endl;
        }
    } finally;

    return std::invoke(std::forward<Func>(f), std::forward<Ts>(args)...);
}

Now:

  • handles void return type (without specialization required).
  • Log also in case of exception (You can go further with std::uncaught_exceptions or try/catch block to dissociate exception from normal path).
  • handle any invocable with its parameters.

For automatic name, we have to rely on MACRO:

#define CallFunctionPrintTime(F, ...) callFunctionPrintTime(#F, F __VA_OPT__(,) __VA_ARGS__)

Demo

like image 41
Jarod42 Avatar answered Sep 19 '22 00:09

Jarod42