0
#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, says

    Such 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 of new(m_closure_global) RawFunctor(std::forward<Functor>(ftor) ); or new(m_closure_local) RawFunctor(std::forward<Functor>(ftor) );.

  • The assigned functor's alignments
    a functor,whose size is less than 17, is assigned to m_closure_local aligned by 16 and other is assigned to m_closure_global aligned by sizeof(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 by alignof(T).

  • reading the functor in *this
    if the functor's type is function pointer, then tmal::function will 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 as decltype(functor). but, I don't know if this method is safe..

so hence, my question is that:

  1. Is safe that assigning a pointer to pointer to member function or static function to a 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??. 
}
  1. If it's not guaranteed that placement new always returns its second argument unchanged, Is there any alternative?
tmal
  • 47
  • 6
  • 2
    This looks like a bunch of UB to me. Why are you doing this in the first place? I mean, normal C++ is sufficient for almost all situations, and already quite complex sometimes – JHBonarius Sep 22 '22 at 16:56
  • 1
    Instead of using `reinterpret_cast` you are supposed to save the result of `new(buf)`. Placement `new` is still a New Expression and returns a pointer to the new object. In any case, you can safely pass the member function pointer by using `memcpy` to copy the pointer into a byte array that is large enough, and then copying it back to a member function pointer where you need it. No casting required. – François Andrieux Sep 22 '22 at 16:57
  • 1
    Does this answer your question? [Casting between void \* and a pointer to member function](https://stackoverflow.com/questions/1307278/casting-between-void-and-a-pointer-to-member-function) – fabian Sep 22 '22 at 19:13
  • Any pointer to an object can be cast to a pointer to void and back. A pointer to a member is an object, so it applies. – n. m. could be an AI Sep 23 '22 at 13:45
  • @JHBonarius Thank you for commenting my question. I updated my question more detailed. – tmal Sep 23 '22 at 13:48
  • @François Andrieux Thank you for commenting my question. I updated my question more detailed. I'm implementing to the function wrapper ``tmal::function`` like ``std::function``. because ``tmal::function`` class assigns the functor to ``char[BUFSZ]``, I need to read ``pointer to pointer to function pointer`` through ``pointer to void``. – tmal Sep 23 '22 at 13:52
  • @tmal `void*` and member function pointers are not compatible. You can cast from non-member function pointer to `void*` and back on *some* platforms, it is a conditionally supported feature. There is no such feature (conditional or otherwise) for member function pointers which work very differently from function pointers. – François Andrieux Sep 23 '22 at 14:00
  • @n.1.8e9-where's-my-sharem. my understanding is that ``void*`` can hold to ``int(Adder::**)()``. Is correct? – tmal Sep 23 '22 at 14:27
  • Member functions have a hidden `this` parameter, thus the function signatures are not compatible. Using this raw casting is kind of dangerous, as nothing is checked. When things are UB, the compiler can do whatever. Did might work, but more likely stuff will break – JHBonarius Sep 23 '22 at 17:24
  • I don't understand what "can hold to" means in this context. – n. m. could be an AI Sep 23 '22 at 18:08
  • @JHBonarius I know that member function pointer is different from static function pointer, and ``void*`` cant contain them. But Im having doubt about if assigning pointer to pointer to member function to ``void*`` is safe. I treat MFP to an object.. and void* hold its address. e.g. ```struct PMFP { int(Adder::*a)(); } a; void* p = &a;``` – tmal Sep 24 '22 at 01:17
  • @n.1.8e9-where's-my-sharem. I mean if ``void*`` can contain the address of ``int(Adder::*mfp)()``. – tmal Sep 24 '22 at 01:27
  • `void*` can point to any object, including this one. – n. m. could be an AI Sep 24 '22 at 05:47
  • _"But Im having doubt about if ... is safe."_. Honestly, if you're looking for safe behavior, you shouldn't be doing anything like this. Don't mess with raw pointers and C casting. There are all kind of standardized techniques for function pointers and type erasure and such in the standard now. Try to use those. – JHBonarius Sep 24 '22 at 07:20

0 Answers0