1

Does it violate the C++ standard (any version of it) in any way to reinterpret_cast from one class method to a completely different class method pointer if neither class are derived from one another or related in any way, as long as the pointer is not dereferenced until it is reinterpret_casted back to the proper class object?

Are the types of different class method pointers different in any implementation of the standard? (For example, perhaps if one class is polymorphic and the other is not)

This post says that this usage of reinterpret_cast follows the standard: https://stackoverflow.com/a/573345

This post says that casting from method pointer to function pointer is UB, which is not what I'm doing but is similar in theory: https://stackoverflow.com/a/6908641

Edit: I need run-time generic-ness as I'm holding a vector of these pointers, and since templates are evaluated at compile-time this doesn't quite work. I kind of need to subvert the type system in order to hold a generic pointer, and to know that when I cast it back to it's original type that it will not be mangled.

Pseudo-code of what I'm trying to achieve:

// A type for Foo method pointer
typedef bool (Foo::*FOO_METHOD_PTR)( int some_arg );

// A generic method pointer type
class UnusedClass {};
typedef void (UnusedClass::*GENERIC_METHOD_PTR)( void );

// generically store a method from any class
GENERIC_METHOD_PTR generic_ptr = reinterpret_cast<GENERIC_METHOD_PTR>( &Foo::method );

// cast back to proper class
FOO_METHOD_PTR foo_ptr = reinterpret_cast<FOO_METHOD_PTR>( generic_ptr );

// macro to help call method pointers
#define CALL_METHOD(object,ptrToMethod)  ((object).*(ptrToMethod))

// call
Foo foo;
CALL_METHOD(foo,foo_ptr)
Brandon
  • 483
  • 3
  • 12
  • 3
    Any reason you don't just abstract all of this behind a `std::function`, or better yet, use a template function? That lets you have any type of callable. – NathanOliver Jul 08 '19 at 16:18
  • Is your question theoretical or do you actually need this to implement something? If it's the latter, then there are probably better ways as NathanOliver suggested. – r3mus n0x Jul 08 '19 at 16:46
  • I'm using this in a project. Basically, I've got a set of text files with a syntax in them to declare objects of arbitrary type to instantiate and a list of member names and values to initialize them to (via Setter methods). Templates will not work because I need to do this at run-time (I'm packing the generic method pointers into an std::vector). This is for a game engine, and it appears that the reinterpret_cast is working wonderfully on my system, I just worry about it's portability. I should have considered std::function, but would love to hear about the portability of my reinterpret_cast. – Brandon Jul 08 '19 at 17:34
  • Because I'm packing these into an std::vector as an std::pair of a generic method pointer and an identifier that tells me how to reinterpret_cast it back to the class the method belongs to. I don't know that I can make an std::vector< std::function<> > that will work this generically, as it is template based, but I'll look into that if needed. – Brandon Jul 08 '19 at 17:40
  • Also, it's important that I have a vector of Setter pointers, because objects will be instantiated frequently throughout the program and I want it to be as fast as possible, instead of simply just doing a butt-load of if ( read(str) == "member" ) SetMember() – Brandon Jul 08 '19 at 18:52
  • If you have a vector of pointers to functions with different types, you'll need some separate code that converts those functions back to the correct types to call them, which means you could have just had a separate container for each actual function type (perhaps via a variable template or member of a class template). In general, `reinterpret_cast` never solves a problem of "I need to do this at run-time". – aschepler Jul 08 '19 at 22:46

2 Answers2

2

The standard does guarantee the behaviour you ask about: (C++17 [expr.reinterpret.cast]/10)

A prvalue of type “pointer to member of X of type T1” can be explicitly converted to a prvalue of a different type “pointer to member of Y of type T2” if T1 and T2 are both function types or both object types. The null member pointer value is converted to the null member pointer value of the destination type. The result of this conversion is unspecified, except in the following cases:

  • converting a prvalue of type “pointer to member function” to a different pointer to member function type and back to its original type yields the original pointer to member value.
  • [...]

So yes, you can reinterpret_cast one pointer-to-member function to another one for purposes of storing it, and then retrieve the original value with the inverse of the cast.

Of course, you could not try calling the function through the intermediate pointer-to-member.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • Thank you, this is what I was looking for. I'm also wondering, With an object ptr of a derived type casted to its base class, a ptr to a method that exist only in the derived class be reinterpret_casted and called from the base class. But I'd like to make sure it's not UB. example: https://pastebin.com/cNzqu99s – Brandon Jul 08 '19 at 22:43
  • @Brandon you should post a new question with that code sample, rather than asking in comments – M.M Jul 08 '19 at 22:53
1

As far I know, tricks with direct calls to method without class context are unspecified by standard. Also they're strictly not recommended because it depends on compiler, platform, etc.
Actually depending on your task, you can use variadic templates for call any method with any parameters, like that:

template<typename TClass, typename TResult, typename ... Type>
struct SmartCall {
  // call for non-const class methods
  static TResult Invoke(TClass* context, TResult (TClass::*method)(Type...), Type... args) {
    return (context->*(method))(args ...);
  }

  // call for const class methods
  static TResult Invoke(TClass* context, TResult (TClass::*method)(Type...) const, Type... args) {
    return (context->*(method))(args ...);
  }
};

All fine, you can use it for call const and non-const methods from any class which returns something excepting void. But what about returning void? We need separated template specialization for it:

// SmartCall specialization for 'void' return type
template<typename TClass, typename ... Type>
struct SmartCall<TClass, void, Type...> {
  static void Invoke(TClass* context, void (TClass::*method)(Type...), Type... args) {
    (context->*(method))(args ...);
  }
  static void Invoke(TClass* context, void(TClass::*method)(Type...) const, Type... args) {
      return (context->*(method))(args ...);
  }
};

Usage example:

    std::string a = "Hello World";

    // same as    size = a.size();
    size_t size =  SmartCall<std::string, size_t>::Invoke(&a, &std::string::size);

    // same as    symbol_w_pos = a.find_first_of("W", 0);
    size_t symbol_w_pos = SmartCall<std::string, size_t, const char*, const std::size_t>::Invoke(&a, &std::string::find_first_of, "W", 0);

    // same as    a.clear();
    SmartCall<std::string, void>::Invoke(&a, &std::string::clear);
alexb
  • 169
  • 6
  • It is in fact valid to `return` an expression with type `void` when the function's own return type is also `void`. No extra specialization should be needed. – aschepler Jul 08 '19 at 22:41