3

Dear Stackoverflow community,

I'm still bit fresh in c++ and I've been scratching my head and haven't found a solution to my problem yet. I've been searching and trying things for a while now and I've gotten to the point where asking a question would be more beneficial and educational.

Problem:

I'd like to make a class or function that wraps/decorates a given function with or without parameters. Like a good old fashioned @wrapthis in python or c# and the like.

The closest thing I found so far (that is elegant, short and easy to use) is from this stackoverflow answer: C++ Equivalent Decorator

The scratching-my-head part is trying to pass a pointer-function. The error I'm receiving:

Error (active) E0300 a pointer to a bound function may only be used to call the function

Which obviously means that somehow passing a pointer in this fashion is not allowed, so what are my options here?

A working example as answer would be great!

Example of what I'm trying to achieve can be found below:

Something.h

class Something
{
private:
public:
    Something() {}
    void v_func_with_nothing() { std::cout << "v_func_with_nothing" << "\n"; }
    void v_func_with_void(void) { std::cout << "v_func_with_void" << "\n"; }
    void v_func_with_one_arg(int x) { std::cout << "v_func_with_one_arg" << x << " " << "\n"; }
    void v_func_with_args(int x, int y) { std::cout << "v_func_with_args" << x << " " << y << "\n"; }

    int return_func_with_nothing() { return 1; }
    int return_func_with_void(void) { return 3; }
    int return_func_with_one_arg(int x) { return x; }
    int return_func_with_args(int x, int y) { return x+y; }
};

Decorator.h [Again source: C++ Equivalent Decorator]

template<typename T>
auto decorator(T&& func)
{
    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;
}

main.cpp

#include <iostream>
#include "Something.h"
#include "Decorator.h"

int main()
{
    Something* something = new Something();       
    auto somedeco = decorator(&something->return_func_with_one_arg);//<-- error here in argument   
    //int value = somedeco(**enter an argument**);
    //std::cout << value << "\n";  
    return 0;
}

Thank you!

EDIT: SOLUTION

Based on the kind answers given down below I thought of editing this post with an example. The solution to the problem was using lambda.

Decorator.h: I created 2 decorators (one for return-functions, one for void-functions):

template<typename T>
auto DECO_R(T&& func)
{
    try
    {
        auto new_function = [func = std::forward<T>(func)](auto&&... args)
        {
            std::cout << "BEGIN RETURN decorating...\n";
            auto result = func(std::forward<decltype(args)>(args)...);
            std::cout << "END RETURN decorating\n";
            return result;
        };
        return new_function;
    }
    catch (const std::exception& ex)
    {
        std::cout << ex.what() << "\n";
    } 
}

template<typename T>
auto DECO_V(T&& func)
{
    try
    {
        auto new_function = [func = std::forward<T>(func)](auto&&... args)
        {
            std::cout << "BEGIN VOID decorating...\n";
            func(std::forward<decltype(args)>(args)...);
            std::cout << "END VOID decorating\n";
        };
        return new_function;
    }
    catch (const std::exception& ex)
    {
        std::cout << ex.what() << "\n";
    }
}

Main.cpp: 2 examples

int main()
{
    Something* something = new Something();
    
    auto somedeco = DECO_R(
        [&](int x) {
            return something->return_func_with_one_arg(x);
        });
    
    int value = somedeco(255);
    std::cout << value << "\n";

    auto some_v_deco = DECO_V(
        [&](int x) {
            return something->v_func_with_one_arg(x);
        });

    some_v_deco(2);

    return 0;
}

Output

BEGIN RETURN decorating...
END RETURN decorating
255

BEGIN VOID decorating...
v_func_with_one_arg2
END VOID decorating

I hope this helps others out there.

Hades
  • 865
  • 2
  • 11
  • 28
  • you need an object to call a member function. Now there are several options: Do you want to wrap the instance into `somedeco`, or should `somedeco` take the instance as parameter when called? – 463035818_is_not_an_ai Jul 14 '20 at 10:34
  • @idclev463035818 isn't 'something' my object I call it from? But to answer your question maybe wrapping the instance into somedeco? because from a visual perspective knowing what datatype it returns is bit unclear. Eitherway what do you suggest? – Hades Jul 14 '20 at 10:38
  • Functions in an object are static, they do not have a different per-object pointer. – Michael Chourdakis Jul 14 '20 at 10:40
  • @MichaelChourdakis Non-static member functions do, i.e. `this`, but to get that you have to first get into the function, which requires calling it on a given instance in the first place ;-) Your point I presume is that the syntax to get a pointer-to-member-function would rather be `&Something::return_func_with_one_arg`, i.e. that the pointer itself has no instance attached and needs that provided upon calling. – underscore_d Jul 14 '20 at 10:54
  • Also, as @idclev463035818 indicated, there's no legitimate use for `new` here. Prefer instead `auto something = Something{};` – underscore_d Jul 14 '20 at 10:55

3 Answers3

3

The call decorator(&something->return_func_with_one_arg) is invalid. There's no such thing as a pointer to a bound function in C++.

If you want somedeco to be a function-like object that wraps a call to something->return_func_with_one_arg(42), for example, you will need to wrap the call either in a lambda:

auto somedeco = decorator(
    [&]() {
        return something->return_func_with_one_arg(42);
    }
);
somedeco();

Or you could pass the parameter through the decorator:

auto somedeco = decorator(
    [&](int x) {
        return something->return_func_with_one_arg(x);
    }
);
somedeco(42);

Keep in mind that this will require that the object pointed to by something outlives the object returned by decorator.

Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
2

There is no simple 1:1 replacement for Pythons dynamic typing in C++. Your example does not compile, because there are no pointer to member function of one specific instance. Pointer to member functions always need an instance to be called. For one simple case I would suggest to use a lambda:

int main() {
    Something something;
    auto somedeco = [&](auto param) {
        // before call
        auto v = something.return_func_with_one_arg(param);
        // after call
        return v;
    };
    return somedeco(1);
}

As you can see the whole machinery of decorate isn't really needed, because with a lamdda you can write the wrapped function inline. On the other hand, the decorator allows you to reuse // before call and // after call for different methods. To fix your code you could also pass the lambda to decorate.

PS: Don't use new to create objects.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
0

you need bind it

int main()
{
    Something* something = new Something();       
    
    using std::placeholders::_1;
    auto f = std::bind( &Something::return_func_with_one_arg, something, _1 );
    auto somedeco = decorator( f );//<-- error here in argument   
    //int value = somedeco(**enter an argument**);
    //std::cout << value << "\n";  
    return 0;
}

https://godbolt.org/z/zdYW9q