6

I have spent the last few days trying to create a generalised wrapper of sorts for function pointers in C++ and I've managed to solve nearly every single issue bar one. My main goal with this was to be able to simply call an object as a function with a function pointer stored internally. If the pointer was pointing somewhere then it would call it as normal while a null pointer would just not call the function and it would continue on as if nothing happened. I intend to use this primarily for callback function purposes, where I would likely not care if the function was called or not and just wanted to preform an action. It works almost perfectly with the following:

template<typename T>
class Action;

template<typename TReturn, typename ... TArgs>
class Action<TReturn(TArgs...)> {
public:
    //! Define a type for the Action object
    typedef TReturn(*signature)(TArgs...);

    //! Constructors
    inline Action(const signature& pFunc = nullptr) : mPointer(pFunc) {}
    inline Action(const Action& pCopy) : mPointer(pCopy.mPointer) {}

    //! Operator Call
    inline bool operator() (TReturn& pReturn, TArgs ... pArgs) const { if (!mPointer) return false; pReturn = mPointer(pArgs...); return true; }

    //! Operators
    inline Action& operator=(const Action& pCopy) { mPointer = pCopy.mPointer; return *this; }
    inline Action& operator=(const signature& pFunc) { mPointer = pFunc; return *this; }
    inline operator bool() const { return (mPointer != nullptr); }

private:
    //! Store a pointer to the callback function
    signature mPointer;
};

template<typename ... TArgs>
class Action<void(TArgs...)> {
public:
    //! Define a type for the Action object
    typedef void(*signature)(TArgs...);

    //! Constructors
    inline Action(const signature& pFunc = nullptr) : mPointer(pFunc) {}
    inline Action(const Action& pCopy) : mPointer(pCopy.mPointer) {}

    //! Operator Call
    inline bool operator() (TArgs ... pArgs) const { if (!mPointer) return false; mPointer(pArgs...); return true; }

    //! Operators
    inline Action& operator=(const Action& pCopy) { mPointer = pCopy.mPointer; return *this; }
    inline Action& operator=(const signature& pFunc) { mPointer = pFunc; return *this; }
    inline operator bool() const { return (mPointer != nullptr); }

private:
    //! Store a pointer to the callback function
    signature mPointer;
};

However, I feel like the situation that would most likely use this wrapper is the output of debug information or formatted text. This could be through user defined functions or inbuilt functions such as printf. To match with printf's signature an Action would be created like:

Action<int(const char*, ...)> callback = printf;

and it would be able to operate the same way that any other Action would behave. The problem I'm finding is the '...' will force the template signature to not align with either of the specialisations, instead going with the first which is only a prototype.

I can fully understand why this doesn't work and why the compiler would not be able to handle the generation of the required class but I was hoping that someone here would know any sneaky ways to either achieve this or something similar. Any help would be much appreciated, thanks :)

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • 7
    Why not use `std::function`? – StoryTeller - Unslander Monica Jul 11 '17 at 06:12
  • What is the purpose of `Action`? What requirement do you have that you can't use [`std::function`](http://en.cppreference.com/w/cpp/utility/functional/function)? What problem do `Action` solve that `std::function` doesn't? – Some programmer dude Jul 11 '17 at 06:14
  • 1
    https://stackoverflow.com/questions/18370396/why-cant-stdfunction-bind-to-c-style-variadic-functions gave some commentary, on how to achieve nearly this. I think for each variadic function, you would need a concrete class to help you, and hope there was a v* variant of the function you could call. – mksteve Jul 11 '17 at 06:28
  • 1
    @Someprogrammerdude When i started this i (wrongly) thought it would be a quick and easy thing I could implement to avoid having to write if statements everywhere i wanted to raise a callback. The problem i was trying to avoid with std::function is that i would still need to check if it was nullptr before calling it. Action was meant to eliminate my need for the checks – MitchellCroft Jul 12 '17 at 02:05

1 Answers1

1

The following example works for all function types, and lambdas with no capture:

#include <utility>
#include <cstdio>
#include <cmath>

template<typename Fn>
class Action {
    Fn* function_ptr;        
public:
    Action() noexcept : function_ptr(nullptr) {}    
    Action(std::nullptr_t) noexcept : function_ptr(nullptr) {}    
    Action(const Action& other) : function_ptr(other.function_ptr) {}    
    Action(Fn f) : function_ptr(f) {}

    Action& operator=(const Action& other) {
        return (function_ptr = other.function_ptr, *this);
    }            
    Action& operator=(std::nullptr_t ) {
        return (function_ptr = nullptr, *this);
    }    
    Action& operator=(Fn f) {
        return (function_ptr = f, *this);
    }

    template<typename... Params>
    auto operator()(Params&&... params) {
        return function_ptr(std::forward<Params>(params)...);
    }
};

Live Demo

As the comments under your question mention, using std::function is better than writing a wrapper for function pointers. The only problem with std::function is that it cannot be used with function signatures that contain ellipsis. If you explicitly specify the signature you can store e.g. printf as follows:

std::function<int(const char*, int, double, double)> fn = printf;

If you use C++17 or Boost, you can implement you own printf-like function, using std::any or Boost.Any which can be assigned to std::function as follows:

#include <iostream>
#include <string>
#include <vector>
#include <any>
#include <functional>

using namespace std;

void any_printf(string&& format, vector<any>&& args) {
    int arg_index = 0; enum { NORMAL, CONVERT } mode;

    for(auto& chr : format) {
        if(mode == CONVERT) {
            switch(chr) {
            case 'd': cout << any_cast<int>(args[arg_index++]); break;
            case 'f': cout << any_cast<float>(args[arg_index++]); break;
            case 's': cout << any_cast<string>(args[arg_index++]); break;
            /* ... */
            default: cout << chr;
            };
            mode = NORMAL;
        }
        else {
            chr == '%' ? (mode = CONVERT, 0) : (cout << chr, 0);
        }
    }
}

int main() {
    using namespace string_literals;
    function<void(string&&, vector<any>&&)> f_ptr { any_printf };

    f_ptr("Cuboid: %smm x %dmm x %fmm.\n", { any("3"s), any(4), any(6.67f) });
    return 0;
}
Akira
  • 4,385
  • 3
  • 24
  • 46
  • That is an interesting way of going about getting it to work. My only problem with that is it requires a value to exist in order to get the signature for the template. It's not as easy for the simple deceleration of desired function callbacks without at least a single function in mind to fit the situation. I had guessed the exact type of syntax I was after would be near if not impossible. Thanks for the quick reply :) – MitchellCroft Jul 12 '17 at 02:38
  • 1
    @MitchellCroft, when you write a function which will invoke a callback, you have to define the signature of it. If you wish **to invoke that callback with variable number of data and with variable types** (like `printf` can be invoked), you can use e.g. an `std::vector` of [`std::any`](http://en.cppreference.com/w/cpp/utility/any) type. This is a C++17 feature, but [Boost.Any](http://www.boost.org/doc/libs/1_64_0/doc/html/any.html) also can be used as a substitution. – Akira Jul 12 '17 at 07:32
  • @MitchellCroft, I edited my answer with the `std::any` approach I mentioned in the previous comment. – Akira Jul 12 '17 at 09:45