5

I have been excited by item 24 of Scott Meyer's book "Effective Modern C++". He mentions the possibility to write a C++14 lambda to record the time taken in an arbitrary function invocation.

I am still in an early of learning C++14 features. My attempt (Main.cpp) looks like this for measuring the time of a member function call:

#include <chrono>
#include <iostream>

auto measure = [](auto&& function, auto&&... parameters) -> decltype(function)
{
    const std::chrono::steady_clock::time_point startTimePoint =
    std::chrono::steady_clock::now();

    const auto returnValue = std::forward<decltype(function)>(function)(
            std::forward<decltype(parameters)>(parameters)...);

    const std::chrono::steady_clock::time_point stopTimePoint =
    std::chrono::steady_clock::now();

    const std::chrono::duration<double> timeSpan = std::chrono::duration_cast<
    std::chrono::duration<double>>(stopTimePoint - startTimePoint);

    std::cout << "Computation took " << timeSpan.count()
    << " seconds." << std::endl;

    return returnValue;
};

class Test
{
public:

    int computation(double dummy)
    {
        std::cout << "Received " << dummy << ". Computing..." << std::endl;

        return 123;
    }
};

int main(int, char**)
{
    Test instance;

    using Function = int (Test::*)(double);
    Function function = instance.computation;

    int result = measure(function, 1.0);

    std::cout << "Result: " << result << std::endl;

    return 0;
}

I get the following compilation errors:

..\src\Main.cpp: In function 'int main(int, char**)':
..\src\Main.cpp:43:36: error: cannot convert 'int (Test::*)(double)' to 'int' in initialization
    int result = measure(function, 1.0);
                                                                        ^
..\src\Main.cpp: In instantiation of '<lambda(auto:1&&, auto:2&& ...)> [with auto:1 = int (Test::*&)(double); auto:2 = {double}; decltype (function) = int (Test::*&)(double)]':
..\src\Main.cpp:43:36:   required from here
..\src\Main.cpp:9:69: error: must use '.*' or '->*' to call pointer-to-member function in 'std::forward<int (Test::*&)(double)>((* & function)) (...)', e.g. '(... ->* std::forward<int (Test::*&)(double)>((* & function))) (...)'
    const auto returnValue = std::forward<decltype(function)>(function)(
                                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        std::forward<decltype(parameters)>(parameters)...);
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~                

Obviously I am doing it wrong, but I could not figure out how to do it right. Can anybody help me? Thank you very much!

ΦXocę 웃 Пepeúpa ツ
  • 47,427
  • 17
  • 69
  • 97
Benjamin Bihler
  • 1,612
  • 11
  • 32
  • 2
    Your lambda returns a wrong type, look at [std::result_of or std::invoke_result](https://en.cppreference.com/w/cpp/types/result_of). You might also want to use [std::invoke](https://en.cppreference.com/w/cpp/utility/functional/invoke). Also note that time measurements have some precision/overhead (typically hundreds of nanoseconds), therefore measuring of runtime of very small functions might not give relevant data. – Daniel Langr Dec 12 '18 at 07:36
  • Why do you want `measure` to be a lambda? – n. m. could be an AI Dec 12 '18 at 07:57
  • 1
    @DanielLangr `std::invoke` is part of [tag:C++17]. The question targets [tag:C++14]... – gsamaras Dec 12 '18 at 08:12
  • @DanielLangr I have tried to do something like `using ReturnType = std::result_of::type;` but my compiler won't accept it. Could you give me another hint wo to use `std::result_of`? Thank you! – Benjamin Bihler Dec 12 '18 at 08:24
  • @n.m. It looks handy if it works and I am studying the C++11 and C++14 features. – Benjamin Bihler Dec 12 '18 at 08:27

3 Answers3

2

There are two ways to approach this task.

  1. Accept a function (or a function object), return a modified function that does the same thing the original function does, plus measures the time. The returned object type cannot be the same as the accepted parameter type. It must be a lambda (or a custom class type, but the lambda is simpler). The actual measurement is performed when the returned object is invoked. Example usage syntax:

    result = measure(foo)(param1, param2);  // variant 1
    auto measured_foo = measure(foo);
    result = measured_foo(param1, param2);  // variant 2
    
  2. Accept a function (or a function object) and its parameters, call it and perform the measurement. The return type is that of the original function. Example usage syntax:

    result = measure(foo, param1, param2);
    

Your measure is closest to the second variant, the only thing that is wrong with it is the declaration. This is the correct one:

auto measure = [](auto&& function, auto&&... parameters) -> decltype(auto)

To be precise, this is not the only wrong thing. If the measured function returns a reference, the return type will be wrong. To fix this, replace

const auto returnValue = ...

with

decltype(auto) returnValue = ...

in the body of the lambda

The other thing that is wrong with your program (but not the measure itself) is the way you are trying to use a member function.

Function function = instance.computation;

This just doesn't work. Use a lambda or std::bind to create a bound member function. There's about a zillion questions (and great answers) about the correct way to do it on stackoverflow.

Live demo (with return by reference working).

If you want the first way of creating a measured function, here's how:

auto measure = [](auto&& function) -> decltype(auto)
{
    return [=](auto&&... parameters) mutable -> decltype(auto) {

        const std::chrono::steady_clock::time_point startTimePoint = 
            std::chrono::steady_clock::now();

        decltype(auto) result = function(std::forward<decltype(parameters)>(parameters)...);

        const std::chrono::steady_clock::time_point stopTimePoint =
            std::chrono::steady_clock::now();

        const std::chrono::duration<double> timeSpan = std::chrono::duration_cast<
        std::chrono::duration<double>>(stopTimePoint - startTimePoint);

        std::cout << "Computation took " << timeSpan.count()
                << " seconds." << std::endl;

        return result;
    };
};

Live demo (with return by reference).

Pay special attention to religious use of decltype(auto). Also mutable in the second version.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • Thank you for your great explanations, the live demo and even giving an alternative approach! I will spend a lot of time studying your code, you can be sure of that! :) – Benjamin Bihler Dec 12 '18 at 09:30
1

Have no clue what you are trying to do there but I am taking my guess here what I have done if this is what you are trying to do:

#include <chrono>
#include <iostream>
#include <functional>

auto measure = [](auto function, auto&&... parameters) -> decltype(function(parameters...))
{
    const std::chrono::steady_clock::time_point startTimePoint =
    std::chrono::steady_clock::now();

    auto returnValue = function(parameters...);

    const std::chrono::steady_clock::time_point stopTimePoint =
    std::chrono::steady_clock::now();

    const std::chrono::duration<double> timeSpan = std::chrono::duration_cast<
    std::chrono::duration<double>>(stopTimePoint - startTimePoint);

    std::cout << "Computation took " << timeSpan.count()
    << " seconds." << std::endl;

    return returnValue;
};

class Test
{
public:

    int computation(double dummy)
    {
        std::cout << "Received " << dummy << ". Computing..." << std::endl;

        return 123;
    }
};

int main(int, char**)
{
    Test instance;

    auto func = std::bind(&Test::computation, &instance, std::placeholders::_1);

    int result = measure(func, 1.0);

    std::cout << "Result: " << result << std::endl;

    return 0;
}
Abdurrahim
  • 2,078
  • 1
  • 17
  • 23
  • Why you have no clue? OP is exploring C++14 features... This answer should provide code that solve the compilation errors, and gets executed smoothly, good job. However, you *need to add some explanation*, others might have no clue what you are trying to do here... :) – gsamaras Dec 12 '18 at 08:43
  • Simple if he writes I want to do is implement a function that takes instance parameters etc etc etc I could have wrote it like that. There are infinite ways to do same thing right. You see I have eliminated all compile errors so what next will happen either is he will say no no I wanted to use it like this or yes this is what I wanted :) Also you see I am also using C++14 features so here is the answer you can take this as an example, – Abdurrahim Dec 12 '18 at 08:45
  • 1
    Abdurrahim, if a question is unclear, then you should comment and ask for more information, and not post answers that are based on assumption, as you claim. I believe this is not the case though, so, if I were you, I would add some explanation... – gsamaras Dec 12 '18 at 08:48
  • @Abdurrahim Thank you very much! This is indeed what I have been trying to do. Two things I do not understand: Scott Meyers passed the function as a universal reference (`auto&& function`). Why did you decide not to do that? Scott Meyers implemented the function call like this in my initial code with `std::forward(func)(...)`. Does this have any advantage over your code? – Benjamin Bihler Dec 12 '18 at 08:55
  • This solution omits perfect forwarding of function and its arguments. @BenjaminBihler Yes, it has the advantage that all arguments are passes _as they are_, preserving their value categories. Without perfect forwarding, all arguments will be treated as lvalues. – Daniel Langr Dec 12 '18 at 08:59
  • I have tried it... my impression is that after Abdurrahim has fixed the flaws in my code, his solution can easily be extended to use perfect forwarding. – Benjamin Bihler Dec 12 '18 at 09:06
  • "Does this have any advantage over your code" yes if you are going to return rvalue. Same for your parameters as well lets say you want to pass functions like this "int a(double && x) { return x; }" then your measure template will need something like this "function(std::forward(parameters)...)" for calling your function and decltype your return value type. Forward will normalize reference and allow you to define more generic templates. But I prefer readability; For real generic functions follow concepts hope it will hit stdc++ in 2020 as claimed, – Abdurrahim Dec 12 '18 at 15:20
0

If you dislike taking a function pointer of a member function (see here) for any reason, good old macros can come to your rescue. Some folks suggest to minimize macro usage, but in this case, I have found it to be more intuitive and readable and easy. Any arbitrary function (including call to a public member function of a class that returns a type) can be timed using the following macro.

#define timefnrt(W, X, Z){\
    time_t V = time(NULL);\
    W = X;\
    time_t Y = time(NULL);\
    Z = difftime(Y, V);\
};

Live code demonstration

In case, the function returns a void, such functions can be timed thus:

#define timefnvoid(X, Z){\
    time_t V = time(NULL);\
    X;\
    time_t Y = time(NULL);\
    Z = difftime(Y, V);\
};
Tryer
  • 3,580
  • 1
  • 26
  • 49