#include <iostream>
struct Adder {
int a, b;
int operator()() { // member function.
return a+b;
}
};
int main() {
using MemFuncPtr = int(Adder::*)() ;
alignas(64) char buf[64]; // closure object (size <= 64) is stored in this.
new(buf) MemFuncPtr(&Adder::operator() ); // *(MemFuncPtr**) buf = MemFuncPtr(&Adder::operator() ).
std::cout << (Adder{10,20}.**reinterpret_cast<MemFuncPtr*>(buf) ) (); // call member function.
reinterpret_cast<MemFuncPtr*>(buf)->~MemFuncPtr(); // casting (char*) to (MemFuncPtr*), and call the destructor of (MemFuncPtr).
}
/** output **
* 30
************/
My understanding of void* is that it's used to "general pointer" because any pointer to an object can be assigned to it with no explicit conversion, and char*and void* use byte addressing so that the size is enough to hold an address of any object but an function address(I heard that on some machines, function addresses can be very large, bigger than any data pointers.)
I'm writing some class that is kind of function wrapper like std::function, so I need to read a member function pointer through char*).. the above code is showed the main idea of my implementation( other closure type is called a bit differently ).
the code is worked fine(compiled on x64, g++-7 7.4.0), but the array buf is not for saving a pointer to code pointer. Can void* be used to read a pointer to code pointer??? and be converted a pointer to function pointer??. I don't know if my code is safe..
2022-09-23 EDIT:
the entire code:
#ifndef MYFUNCTIONAL_HPP // {
# include<type_traits>
# include<exception>
# include<typeinfo>
# include<cstring>
# define MYFUNCTIONAL_HPP
namespace tmal {
/********************
* bad_function_call
********************/
class bad_function_call
: public std::exception {
virtual const char* what() const noexcept {
return "bad function call";
}
};
/**********************
* always_false
**********************/
template<typename T>
constexpr bool always_false = false;
/***********************
* decay_function
* decay_function_t
***********************/
template<typename T>
struct decay_function {
using type = T;
};
template<typename Ret, typename...Args>
struct decay_function<Ret(Args...)> {
using type = Ret(*)(Args...);
};
template<typename T>
using decay_function_t = decay_function<T>::type;
/***************
* aligned_storage
***************/
template<size_t align, size_t len>
struct aligned_storage {
alignas(align) char buf[len];
};
/********************
* function
* function<Ret(Args...)>
********************/
template<typename T>
class function {
static_assert(always_false<T>,
"wrong type of template parameter (1,should be \"function\")");
};
template<typename Ret, typename...Args>
class alignas(16) function<Ret(Args...)> {
enum Operation {
TARGET=0, TARGET_TYPE, CONSTRUCT, DESTRUCT
};
using InvokeType = Ret(*)(function&, Args&&...);
using ClosureOperation = void(*)(const function&, void*, Operation);
using ClosureType1 = char[16];
using ClosureType2 = char*;
using ClosureSize = size_t;
/** invoke 'member function' with this pointer */
template<typename Mfp, typename Class, typename...Params>
static auto invoke(function& fn, Class&& thisptr, Params&&...param)
-> std::enable_if_t<std::is_member_function_pointer_v<Mfp> &&
std::is_pointer_v<Class>, Ret> {
if constexpr (sizeof(Mfp) <= 16) {
auto mfp = *reinterpret_cast<Mfp*>(fn.m_closure_local);
return (thisptr->*mfp) (std::forward<Params>(param)...);
}
else {
auto mfp = *reinterpret_cast<Mfp*>(fn.m_closure_global);
return (thisptr->*mfp) (std::forward<Params>(param)...);
}
}
/** invoke 'member function' with this reference */
template<typename Mfp, typename Class, typename...Params>
static auto invoke(function& fn, Class&& thisref, Params&&...param)
-> std::enable_if_t<std::is_member_function_pointer_v<Mfp> &&
std::is_reference_v<Class>, Ret> {
if constexpr (sizeof(Mfp) <= 16) {
auto mfp = *reinterpret_cast<Mfp*>(fn.m_closure_local);
return (thisref.*mfp) (std::forward<Params>(param)...);
}
else {
auto mfp = *reinterpret_cast<Mfp*>(fn.m_closure_global);
return (thisref.*mfp) (std::forward<Params>(param)...);
}
}
/** invoke any other closure */
template<typename Functor>
static auto invoke(function& fn, Args&&...args)
-> std::enable_if_t<std::is_invocable_v<Functor,Args...> &&
std::is_same_v<std::invoke_result_t<Functor,Args...>,Ret>,Ret> {
if constexpr (sizeof(Functor) <= 16) {
return (*reinterpret_cast<const Functor*>(fn.m_closure_local)) (std::forward<Args>(args)...);
}
else {
return (*reinterpret_cast<const Functor*>(fn.m_closure_global)) (std::forward<Args>(args)...);
}
}
/** do operation (closure size <=16) */
template<size_t FunctorSize, typename Functor>
static auto operate(const function& fn, void* out, Operation op)
-> std::enable_if_t<FunctorSize<=16, void> {
switch(op) {
case TARGET: *reinterpret_cast<const char**>(out) = fn.m_closure_local; break;
case TARGET_TYPE: *reinterpret_cast<const std::type_info**>(out) = &typeid(Functor); break;
case CONSTRUCT: new(reinterpret_cast<function*>(out)->m_closure_local )
Functor(*reinterpret_cast<const Functor*>(fn.m_closure_local) ); break;
case DESTRUCT: reinterpret_cast<const Functor*>(fn.m_closure_local)->~Functor(); break;
};
}
/** do operation (closure size >16) */
template<size_t FunctorSize, typename Functor>
static auto operate(const function& fn, void* out, Operation op)
-> std::enable_if_t<(FunctorSize>16), void> {
switch(op) {
case TARGET: *reinterpret_cast<const char**>(out) = fn.m_closure_global; break;
case TARGET_TYPE: *reinterpret_cast<const std::type_info**>(out) = &typeid(Functor); break;
case CONSTRUCT: new(reinterpret_cast<function*>(out)->m_closure_global)
Functor(*reinterpret_cast<const Functor*>(fn.m_closure_global) ); break;
case DESTRUCT: reinterpret_cast<const Functor*>(fn.m_closure_global)->~Functor(); break;
};
}
private:
ClosureType1 m_closure_local;
ClosureType2 m_closure_global = nullptr;
ClosureSize m_closure_maxsz = 16;
InvokeType m_invoke = nullptr;
ClosureOperation m_operate = nullptr;
public:
/** default constructor */
function() = default;
/** copy constructor */
function(const function& other) { *this = other; }
/** move constructor */
function(function&& other) { *this = std::move(other); }
/** create an empty function */
function(std::nullptr_t) {}
/** destructor */
~function() noexcept {
if(m_operate) m_operate(*this, nullptr, DESTRUCT);
if(m_closure_global) delete[] m_closure_global;
}
/** initialize the target with std:;forward<Functor>(ftor) */
template<typename Functor>
function(Functor&& ftor)
requires !std::is_same_v<std::remove_cvref_t<function>, // prevent to forwarding itself.
std::remove_cvref_t<Functor> >{
using RawFunctor = decay_function_t<std::remove_reference_t<Functor>>; // int(&)() => int() => int(*)()
m_operate = function::operate<sizeof(RawFunctor), RawFunctor>;
if constexpr (std::is_member_function_pointer_v<RawFunctor>) {
m_invoke = function::invoke<RawFunctor, Args...>;
}
else {
m_invoke = function::invoke<RawFunctor>;
}
if constexpr (sizeof(RawFunctor)<=16) {
new(m_closure_local) RawFunctor(std::forward<Functor>(ftor) );
}
else {
m_closure_maxsz = sizeof(RawFunctor) + 63 & -64; // always allocate power of 64.
m_closure_global = (new aligned_storage<sizeof(RawFunctor)+63&-64,
sizeof(RawFunctor)+63&-64>)->buf;
new(m_closure_global) RawFunctor(std::forward<Functor>(ftor) );
}
}
/** assign the new target with std::forward<Functor>(ftor) */
template<typename Functor>
auto operator=(Functor&& ftor)
-> std::enable_if_t<!std::is_same_v<std::remove_cvref_t<Functor>, // prevent to forwarding itself.
std::remove_cvref_t<function> >,function&> {
using RawFunctor = decay_function_t<std::remove_reference_t<Functor>>; // int() => int(*)()
if(m_operate) {
m_operate(*this, nullptr, DESTRUCT); // call the current closure's destructor.
}
m_operate = function::operate<sizeof(RawFunctor), RawFunctor>;
if constexpr (std::is_member_function_pointer_v<RawFunctor>) {
m_invoke = function::invoke<RawFunctor, Args...>;
}
else {
m_invoke = function::invoke<RawFunctor>;
}
if constexpr (sizeof(RawFunctor)<=16) {
new(m_closure_local) RawFunctor(std::forward<Functor>(ftor) );
}
else {
if(m_closure_maxsz < sizeof(RawFunctor) ) {
if(m_closure_global) delete[] m_closure_global;
m_closure_maxsz = sizeof(RawFunctor) + 63 & -64; // always allocate power of 64.
m_closure_global = (new aligned_storage<sizeof(RawFunctor)+63&-64,
sizeof(RawFunctor)+63&-64>)->buf;
}
new(m_closure_global) RawFunctor(std::forward<Functor>(ftor) );
}
return *this;
}
/** assign a copy of target of other */
function& operator=(const function& other) {
m_closure_maxsz = other.m_closure_maxsz;
m_invoke = other.m_invoke;
m_operate = other.m_operate;
if(other.m_closure_global) {
m_closure_global = (new aligned_storage<sizeof(RawFunctor)+63&-64,
sizeof(RawFunctor)+63&-64>)->buf;
}
other.m_operate(other, this, CONSTRUCT);
return *this;
}
/** move the target of other to this */
function& operator=(function&& other) {
memcpy(this, &other, sizeof(function) );
other = nullptr;
return *this;
}
/** drop the current target */
function& operator=(std::nullptr_t) {
if(m_operate) m_operate(*this, nullptr, DESTRUCT);
m_invoke = nullptr;
m_operate = nullptr;
return *this;
}
/** invoke the wrapped function */
Ret operator()(Args&&...args) const {
if(m_invoke) {
return m_invoke(*const_cast<function*>(this), std::forward<Args>(args)...);
}
throw bad_function_call{};
}
/** swap the stored targets of *this and other */
void swap(function& other) {
alignas(16) char temp[sizeof(function) ];
memcpy(temp, this, sizeof(function) );
memcpy(this, &other, sizeof(function) );
memcpy(&other, temp, sizeof(function) );
}
/** get type_info of target */
const std::type_info& target_type() const {
if(m_operate) {
const std::type_info* ret;
m_operate(*this, &ret, TARGET_TYPE);
return *ret;
}
else {
return typeid(void);
}
}
/** get target pointer */
template<typename T>
constexpr T* target() const {
char* ret = nullptr;
if(m_operate) {
m_operate(*this, &ret, TARGET);
}
return reinterpret_cast<T*>(ret);
}
};
};
// }
#endif
Example:
# include"myfunctional.hpp"
# include<iostream>
int sum(int a, int b) {
return a + b;
}
struct Adder {
int a, b;
Adder() = delete; // default ctor
~Adder() { std::cout << __PRETTY_FUNCTION__ << std::endl; } // dtor
Adder(int a, int b) :a(a), b(b) { std::cout << __PRETTY_FUNCTION__ << std::endl; } // ctor
Adder(const Adder& ab) :a(ab.a), b(ab.b) { std::cout << __PRETTY_FUNCTION__ << std::endl; } // copy ctor
Adder(Adder&& ab) :a(ab.a), b(ab.b) { // move ctor
std::cout << __PRETTY_FUNCTION__ << std::endl;
ab.a = ab.b = 0;
}
int operator()() const {
return a + b;
}
};
int main()
{
tmal::function<int(int,int)> fn0 = sum; // OK. static function.
std::cout << fn0(10,20) << '\n' // expected 30
<< fn0.target_type().name() << '\n'; // expected PFiiiE
// tmal::function<int(Adder&)> fn1 = &Adder::operator(); // Error. the operator() needs to (const Adder&).
tmal::function<int(const Adder&)> fn1 = &Adder::operator(); // OK. member function pointer.
std::cout << fn1(Adder{10,20} ) << '\n' // expected 30
<< fn1.target_type().name() << '\n'; // expected M5AdderKFivE
tmal::function<int()> fn2 = Adder{0,0}; // OK. functor.
fn2.target<Adder>()->a = 10;
fn2.target<Adder>()->b = 20;
std::cout << fn2() << '\n' << fn2.target_type().name() << '\n'; // expected 30 5Adder
fn2 = [] { return 30; }; // OK. lambda.
std::cout << fn2() << '\n' << fn2.target_type().name() << '\n'; // expected 30 Z4mainEulvE_
}
thanks you for the comments. I wrote my code for some reasons:
placement new's return value
In cppreference new expression, the section,Placement new, saysSuch allocation functions are known as "placement new", after the standard allocation function void* operator new(std::size_t, void*), which simply returns its second argument unchanged
so, I don't use the return value ofnew(m_closure_global) RawFunctor(std::forward<Functor>(ftor) );ornew(m_closure_local) RawFunctor(std::forward<Functor>(ftor) );.
The assigned functor's alignments
a functor,whose size is less than 17, is assigned tom_closure_localaligned by 16 and other is assigned tom_closure_globalaligned bysizeof(functor)+63&-64. I know that memory allocated with new or malloc is aligned by 8 or 16 depending on the platform, but since c++17, the operator new automatically aligns a type of T byalignof(T).reading the functor in
*this
if the functor's type is function pointer, thentmal::functionwill save the functor as pointer to pointer to function (e.g.decltype(functor)==int(Adder::*)(), then call the constructor(int(Adder::*)() )(). for the other types, It will save asdecltype(functor). but, I don't know if this method is safe..
so hence, my question is that:
- Is safe that assigning
a pointer to pointer to member function or static functiontoa pointer to void?(can I treat pointer to pointer to function as data pointer??) for example:
int sum(int a, int b) { return a + b; } // decltype(sum) => int(int,int)
struct Adder {
int a, b;
int operator()()const { // decltype(&Adder::operator()) => int(Adder::*)()
return a+b;
}
};
int main() {
int(Adder::*member_func_ptr)() = &Adder::operator();
int(*static_func_ptr)(int,int) = sum;
void *vp1 = &member_func_ptr, // Is it safe??.
*vp2 = &static_func_ptr; // Is it safe??.
}
- If it's not guaranteed that placement new always returns its second argument unchanged, Is there any alternative?