I've started to write a header-only implementation of callback for member functions :
#include <cstddef>
#include <utility>
namespace _callback_internal{
/** \internal
* A Type list to store variable argument type
*/
template<typename T, typename... Args>
struct TypeRecList{
template<size_t n, bool dummy = true> // dummy is needed for the partial specialization
struct get{
using target = typename TypeRecList<Args...>::template get<n - 1>;
using type = typename target::type;
using tail = typename target::tail;
};
template<bool dummy>
struct get<0, dummy>{
using target = get<0>;
using type = T;
using tail = TypeRecList<Args...>;
};
};
/**
* Canonical definition of TypedCallbackObject, so that the compiler does not complain about unrelated type
* when accessed with ::CallbackObject<method>
*/
template <typename C, typename R, typename... Args>
struct TypedCallbackObject{
C * this_;
R (*callback)(C *, Args...);
template <typename _C>
inline constexpr operator TypedCallbackObject<_C, R, Args...>() {
return TypedCallbackObject<_C, R, Args...>{reinterpret_cast<TypedCallbackObject<_C, R, Args...>>(*this)};
}
template <typename _C>
inline constexpr TypedCallbackObject<_C, R, Args...> & castClass() {
return *reinterpret_cast<TypedCallbackObject<_C, R, Args...>*>(this);
}
};
/**
* Same as TypedCallbackObject, but with a void `this_`
*/
template <typename R, typename... Args>
using CallbackObject = TypedCallbackObject<void, R, Args...>;
template<typename R, typename C, typename... Args>
struct CallbackDetails;
/**
* The callback class, holding addresses of the static version of the members generated at compile time.
*/
template<auto f, typename R, typename C, typename... Args>
struct Callback{
using Self = Callback<f, R, C, Args...>;
/**
* Canonical c function version of the member, taking void as first argument. mfunc permits to reinterpret cast to a function taking any type as first arg.
*/
static R cfunc(void * _this, Args... args){
return (static_cast<C *>(_this)->*f)(args...);
}
/**
* Allow to change the first argument from a void pointer to anything.
*/
template<typename T=void>
static constexpr auto mfunc(){
return reinterpret_cast<R (*)(T *, Args...)>(Self::cfunc);
}
using details_t = CallbackDetails<R, C, Args...>;
using return_t = typename details_t::return_t;
using class_t = typename details_t::class_t;
using args_list_t = TypeRecList<Args...>;
template <typename T=void>
using CallbackObject = typename details_t::template CallbackObject<T>;
template<typename T=void>
static inline constexpr CallbackObject<T> callback(C * this_){
return CallbackObject<T>{
this_,
Self::template mfunc<T>()
};
}
};
/**
* Detail class the extract the information about the callback type.
*/
template<typename R, typename C, typename... Args>
struct CallbackDetails{
using return_t = R;
using class_t = C;
using args_list_t = TypeRecList<Args...>;
using cfunc_t = R(C::*)(Args...);
template<auto f>
using Callback = Callback<f, R, C, Args...>;
template<typename T=void>
using CallbackObject = TypedCallbackObject<T, R, Args...>;
};
/** \internal
* Utility function to be used with `decltype()` to extract the details.
*/
template<typename R, typename C, typename... Args>
CallbackDetails<R, C, Args...> get_details(R (C::*) (Args...));
}
/**
* Callback object class can use to group the function pointer and the first argument (bind-like)
*/
template <typename C, typename R, typename... Args>
using TypedCallbackObject = _callback_internal::TypedCallbackObject<C, R, Args...>;
/**
* Same as TypedCallbackObject with void * as first function argument.
*/
template <typename R, typename... Args>
using CallbackObject = _callback_internal::CallbackObject<R, Args...>;
/**
* Return the internal Callback object providing all informations abour the method.
*/
template <auto method>
using CallbackTraits = typename decltype(_callback_internal::get_details(method))::template Callback<method>;
/**
* Construct a c function pointer that will have the same signature as \p method, but with a `void *` argument (or \p T if provided), wich should be an instance of the class method is bound to.
*/
template <auto method, typename T=void>
inline constexpr auto toCallback(){ return CallbackTraits<method>::template mfunc<T>(); }
/**
* Construct a callback object wich binds an instance to a c function pointer that will call method from the instance.
*/
template <auto method, typename T=void>
inline constexpr auto toCallbackObject(typename CallbackTraits<method>::class_t * instance){ return CallbackTraits<method>::template callback<T>(instance); }
Here a main.cpp to test it :
#include "./callback.h"
#include <iostream>
#include <sstream>
#include <string>
void f(void * instance, std::string (*g)(void *, float, int)){
std::cout<<"g addr : <"<<(void *)g<<"> result:"<<g(instance, 1.5, 9)<<std::endl;
}
struct A{
int coef;
public:
A(int coef):coef{coef} {}
std::string add(float f, int i){
std::ostringstream oss;
oss << "Add function : " << coef << ×ばつ("<< f << "+" << i << ") ) " << coef*(f+i);
return oss.str();
}
std::string mult(float f, int i){
std::ostringstream oss;
oss << "Add function : " << coef << ×ばつ("<< f << "*" << i << ") ) " << coef*(f*i);
return oss.str();
}
};
int main(int argc, char * argv[]){
A a1{1};
A a2{2};
f(&a1, toCallback<&A::add>());
f(&a2, toCallback<&A::add>());
f(&a1, toCallback<&A::mult>());
f(&a2, toCallback<&A::mult>());
CallbackObject<std::string, float, int> oba1 = toCallbackObject<&A::add>(&a1);
f(oba1.this_, oba1.callback);
CallbackObject<std::string, float, int> oba2 = toCallbackObject<&A::add>(&a2);
f(oba2.this_, oba2.callback);
TypedCallbackObject<A, std::string, float, int> oba2_ = toCallbackObject<&A::add, A>(&a2);
f(oba2_.castClass<void>().this_, oba2_.castClass<void>().callback);
return 0;
}
I tried to use the least of C++17 features possible, but I don't see how I could get rid of the auto in the templates...
Do you think removing them is worth it, or should I consider that most compilers are c++17 compatible ? (Note that I would like to target an embedded system... but from what I see, these compilers are mostly based on GCC or Clang...)
Review appreciated too :)
1 Answer 1
Don't do this:
template <typename _C>
Such names are reserved in C++:
Each identifier that contains a double underscore
__
or begins with an underscore followed by an uppercase letter is reserved to the implementation for any use.
That phrase "for any use" means that _C
might be a macro that expands to something unexpected here.
Generally, the code looks over-complex for a simple wrapper around std::mem_fun
that passes the object argument as a void*
.
If you intend to call from a C program, the function pointer should be declared with extern "C"
linkage, as the binary interface may differ between C and C++ calling conventions.
The code assumes that std::size_t
is also defined in the global namespace, but that's not necessarily the case. Best to refer to it by its full name, rather than add another portability risk.
-
\$\begingroup\$
std::mem_fun()
is deprecated in C++17, you should usestd::mem_fn()
instead. Is it really that simple to write a wrapper for it though that does what OP wants? \$\endgroup\$G. Sliepen– G. Sliepen2023年12月18日 16:39:27 +00:00Commented Dec 18, 2023 at 16:39 -
\$\begingroup\$ Thanks - I hadn't even spotted that those two are different things, due to the very similar names! \$\endgroup\$Toby Speight– Toby Speight2023年12月18日 16:42:50 +00:00Commented Dec 18, 2023 at 16:42
-
1\$\begingroup\$ I haven't actually tried to implement it myself, so my perception that it looks complex might be off. That said, looking at how
std::mem_fn
works might be instructive. \$\endgroup\$Toby Speight– Toby Speight2023年12月18日 16:44:11 +00:00Commented Dec 18, 2023 at 16:44
gcc
implementation. But that is true for your soluuib too too. No C++ function is automatically a C function, you have to give it one by using extern "C". You cannot do that for templates. \$\endgroup\$