2

Suppose you have these classes:

class A {};
class B : public A {};
class C : public A {};

class Gen {
public: 
     A* genA(int val);
     B* genB(int val);
     C* genC(int val);
};
    

With these I can do this:

Gen g;
A* a = g.genA(1);
A* b = g.genB(1);
A* c = g.genC(1);

Now, suppose I want to pass the generating function around to avoid having to build an enum and table or some other layer of indirection. (And also suppose that because didn't write Gen I can't just change the return types.) Then I would want something like this typedef:

typedef A* (Gen::*GenMethod)(int);

Unfortunately you can't directly assign &Gen::genB or &Gen::genC to a pointer of that type, nor can you use a static_cast<> to cast it to that type.

You can, however, use a reinterpret_cast<>, which isn't surprising because reinterpret_cast<> lets you do all sorts of things.

In my testing the code using the reinterpret_cast<> for this case works fine. So my questions are:

  1. Is there a better/more specific way of doing this (without building a layer of indirection), and
  2. If not, is this one of those rare cases where a reinterpret_cast<> can be used safely?
skef
  • 21
  • 2
  • I found a [related topic](https://stackoverflow.com/questions/22782166/function-pointers-with-different-return-types) but can't find one specific to your question. – Louis Go May 18 '21 at 03:40
  • It is UB. The compilers are allowed to assume that UB never happens, so a compiler that is able to see through this trick can just delete the cast and surrounding code. – n. m. could be an AI May 18 '21 at 03:55
  • Yeah, actually different return types would pose a more obvious problem. I was expecting `static_cast` to work in this scenario and the fact that it doesn't makes the use of `reinterpret_cast` suspicious. Still, if there's a problem I can't tell where it would come from. – skef May 18 '21 at 03:56
  • So the UB explanation predicts that the optimizer will screw things up. Maybe so. – skef May 18 '21 at 03:58
  • Are you sure it's UB, though? https://stackoverflow.com/a/37118638/15956854 implies things in this area are unspecified rather than undefined, and the former gets different optimizer treatment than the latter. See also https://stackoverflow.com/questions/53995657/is-reinterpret-cast-type-punning-actually-undefined-behavior – skef May 18 '21 at 04:02
  • 1
  • 1
    `std::function<>` does seem to work well for this! – skef May 18 '21 at 06:54
  • @j6t could you write an simple answer form your comment? – Louis Go May 18 '21 at 07:10

1 Answers1

6

You can store these covariant member function pointers in a std::function:

std::function<A*(Gen&, int)> dispatch(&Gen::genB);

and then call it like this:

Gen g;
A* b = dispatch(g, 1);

This works because the return types of the callables stored in a std::function need only be convertible to the type given in the template argument (A *), and need not be exactly of that type. In this example, B * is convertibel to A *.

j6t
  • 9,150
  • 1
  • 15
  • 35