Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the equivalent of Python function decorators in C++?

Tags:

c++

I'm porting a Python metrics library to C++. One API the Python library provides is a function decorator to easily record timing data on functions. By modifying the function definition to be

@timed('timing.foo')
def foo():
    ...

foo_result = foo() is essentially turned into

start = time.time()
foo_result = foo()
post_metric_to_endpoint('timing.foo', time.time() - start)

In Function hooking in C++, we find this answer that wraps instances and places the burden of calling the timing function on the caller, which means we won't get performance data across the codebase (it's annoying to update in the first place, and easily forgotten later). Similarly, this answer to Timing in an elegant way in c++ requires altering the call sites. This other answer to that question provides a way of wrapping arbitrary blocks of code, which theoretically means we could indent the entire body of a function we want to time and nest it inside a scope. This is the closest I've found to what I want, but is pretty ugly, fairly intrusive, and I'm not sure of the performance impact.

Since this is meant to be used as a library, we can modify the source of the functions we want to time; in fact, this is preferable, as I'd like every call of that function to be timed. Most discussions seem to focus on ad-hoc profiling in development, whereas I'm trying to build a system that would be always active in a production environment.

like image 678
Xiong Chiamiov Avatar asked Nov 03 '16 02:11

Xiong Chiamiov


2 Answers

Although C++ does not have explicit language support for decorators, it turns out that you can "emulate" them quite nicely using C++14 generic lambdas. Here is my take on it:

#include <iostream>

template<class T>
auto decorator(T&& func)
{
    // we create a closure below
    auto new_function = [func = std::forward<T>(func)](auto&&... args)
    {
        std::cout << "BEGIN decorating...\n"; 
        auto result = func(std::forward<decltype(args)>(args)...);   
        std::cout << "END decorating\n";
        return result;
    };
    return new_function;
}

int f(int x)
{
    std::cout << x * x << '\n';
    return x * x;
}

auto decorated = decorator(f);
auto double_decorated = decorator(decorator(f));

int main()
{
    decorated(5);
    double_decorated(10);
}

Live on Coliru

Of course inside the decorator you can add whatever you want (including timing etc), above is just a minimal example. If it looks too daunting you can ignore the mumbo-jumbo std::forward and C++14 generalized lambda captures and simply have

#include <iostream>

template<class T>
auto decorator(T func)
{
    // we create a closure below
    auto new_function = [func](auto... args)
    {
        std::cout << "BEGIN decorating...\n"; 
        auto result = func(args...);   
        std::cout << "END decorating\n";
        return result;
    };
    return new_function;
}

int f(int x)
{
    std::cout << x * x << '\n';
    return x * x;
}

auto decorated = decorator(f);
auto double_decorated = decorator(decorator(f));

int main()
{
    decorated(5);
    double_decorated(10);
}
like image 106
vsoftco Avatar answered Sep 21 '22 05:09

vsoftco


C++ doesn't have Pythonic decorators, but it has deterministic destruction. To time a function, create a class that captures a timestamp at construction time, and reports at destruction. Then declare an instance as the first line in the function:

class Timed
{
//...
}

void foo()
{
    Timed t_;

    //Do the rest of work
}
like image 22
Seva Alekseyev Avatar answered Sep 22 '22 05:09

Seva Alekseyev