Search code examples
c++pointersdecoratorfunction-pointerswrapper

How to make a template Wrapper/Decorator in c++17


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.


Solution

  • 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.