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 :)
A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.
In UML models, template parameters are formal parameters that once bound to actual values, called template arguments, make templates usable model elements. You can use template parameters to create general definitions of particular types of template.
Variadic templates are class or function templates, that can take any variable(zero or more) number of arguments. In C++, templates can have a fixed number of parameters only that have to be specified at the time of declaration. However, variadic templates help to overcome this issue.
" typename " is a keyword in the C++ programming language used when writing templates. It is used for specifying that a dependent name in a template definition or declaration is a type.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With