3

An example:

class Base
{
    protected:
        virtual void function() { std::cout << "Base\n"; }
        void callfunction(void (Base::* func)()) { (this->*func)(); }
};

class Derived1 : public Base
{
    public:
        void publicCall() { callfunction(&Derived1::function); }
    private:
        void function() override { std::cout << "Derived1\n"; }
};

I would like to be able to send the derived function pointer to the base class to fulfill a pattern that can be specified in the base class. Thus, reusing code.

However, I'm not sure what the type is supposed to be in the base class since Base::* is not a Derived1::*.

I get the following error: No instance of overloaded function callfunction matches the argument list: arguments types are (Derived1::*)

Now obviously, I could change the data type to be Derived1::*, but that wouldn't allow me to send in other derived class virtual functions back to Base to handle, such as Derived2::function()

Can a template sort of function be created like this?

cwbusacker
  • 507
  • 2
  • 12
  • Your syntax is off: you need `(this->*func)()` and `callFunction(&Derived1::function);`. But yeah, even with that, it doesn't work – Justin Apr 19 '21 at 21:14
  • Thanks @Justin I will fix the syntax as an edit. I've had it written that way before, but still doesn't resolve the problem. – cwbusacker Apr 19 '21 at 21:17
  • 1
    Must your virtual function be protected? It would work if you used `callfunction(&Base::function)`, but that doesn't work unless the virtual function is public.... Or you could make a protected `auto getfunction() { return &Base::function; }` in `Base`. – Justin Apr 19 '21 at 21:18
  • 1
    No. I do not want to make it public since these functions are meant to be restricted to the derived classes and not accessible by the client. That's a workaround, but not a solution. – cwbusacker Apr 19 '21 at 21:20

5 Answers5

2

Use std::function<> instead of C style function pointer.

std::function<returnType (parameterType)> functionPointer = functionName;

Do not forget to include <functional>.

I think this will resolve your problem, but for sure it is preferred.

To pass it directly:

void callfunciton(const std::function<void()>& func);

and call by

callfunction([]{ function(); });

EDIT: Read this to learn why you should use std::function<>.

  • Just fixed that in the post. I've tried using ```std::function``` but for some reason it doesn't seem to recognize it. – cwbusacker Apr 19 '21 at 21:07
  • I guess the follow up question is, how do I send the function as an argument when using std::function<>? I've tried using &function1 and &Derived1::function1 and without the &. – cwbusacker Apr 19 '21 at 21:11
  • Have you included functional? –  Apr 19 '21 at 21:11
  • Yes, but only in the base class where std::function is used. Do I need to ```#include ``` in the derived classes as well? – cwbusacker Apr 19 '21 at 21:13
2

Just send &Base::function. Since it's virtual, the right one will be called.

The only problem here is that Base::function is protected and you cannot access it from Derived (strange but true). OTOH &Derived::function won't convert to void (Base::* func)() (and for a good reason). So you have to either make Base::function public, or provide an accessor:

class Base { 
  ...
  protected:
    auto getFunction() { return &Base::function; }

Live demo

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • Makes sense never thought of that. –  Apr 20 '21 at 02:11
  • IMO, this is the best solution. It's safe (e.g. my answer is very easy to use incorrectly and cause UB), it's clear. It avoids more complicated language features (e.g. templates). Etc. – Justin Apr 20 '21 at 02:18
  • This is my second choice, but I'm dealing with a class of hundreds of functions. Making this solution a lot more difficult to implement. – cwbusacker Apr 26 '21 at 22:25
  • @cwbusacker What about pulling out the virtual functions into an interface? E.g. https://godbolt.org/z/13czdbW85 . – Justin Apr 26 '21 at 22:30
1

I think that a template function should work

template<class _DType>
void Base::callFunction(void(_DType::*func)()) { func(); }

then the function from Derived1

void Derived1::publicCall() { Base::callFunction(&Derived1::function); }

This work as long as Derived1 is derived by Base, that is not what you wrote but I'm assuming you just missed, anyway next time I would add class Derived1: public Base to have a more clear code.

Dharman
  • 30,962
  • 25
  • 85
  • 135
  • 1
    Doing my civic duty: Do not name identifiers with leading underscores followed by uppercase letters. Those names are reserved, and violation of this results in undefined behavior. – Chris Uzdavinis Apr 19 '21 at 21:12
  • @ChrisUzdavinis for what? –  Apr 19 '21 at 21:31
  • `func();` will not compile. `(this->*func)();` will also not compile. – n. m. could be an AI Apr 19 '21 at 21:35
  • `(static_cast<_DType*>(this)->*func)()` – Justin Apr 19 '21 at 21:38
  • @CEPB — they’re reserved for use by the implementation. There’s nothing that prohibits the standard library from defining a macro named `_DType`. – Pete Becker Apr 19 '21 at 21:41
  • @PeteBecker tnx, is there a list for these, so I can read on them? I just learned that `__something` is reserved for std library. –  Apr 19 '21 at 21:45
  • The ones you need to avoid are names that begin with an underscore followed by a capital letter and names that **contain** two consecutive underscores. That second one is an artifact from the days of CFront, which used names with two underscores in the C code that it generated. – Pete Becker Apr 19 '21 at 21:48
1

Your question is asking for CRTP, a poorly named but well known label for the idiom described in a 1995 article by Jim Coplien, titled "A Curiously Recurring Template Pattern". The idea is well documented, so I won't go into depth, but the idea is pretty simple: The base class takes a template argument, and the derived class provides its type to the base. Then the base class can "downcast" the this pointer to the derived type and call the function without needing a virtual call.

https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

What is the curiously recurring template pattern (CRTP)?

Chris Uzdavinis
  • 6,022
  • 9
  • 16
1

You can cast the member function pointer to the base class member function pointer type: callfunction(static_cast<void (Base::*)()>(&Derived1::function));. Full example:

class Base
{
    protected:
        virtual void function() { std::cout << "Base\n"; }
        void callfunction(void (Base::* func)()) { (this->*func)(); }
};

class Derived1 : public Base
{
    public:
        void publicCall() { callfunction(static_cast<void (Base::*)()>(&Derived1::function)); }
    private:
        void function() override { std::cout << "Derived1\n"; }
};

This is allowed; see cppreference on pointers to member functions:

Pointer to member function of a base class can be implicitly converted to pointer to the same member function of a derived class ...
Conversion in the opposite direction, from a pointer to member function of a derived class to a pointer to member function of an unambiguous non-virtual base class, is allowed with static_cast and explicit cast

In this case, we are converting derived to base rather than vice-versa, so we need to static_cast.


You can avoid requiring the cast from the call-site by casting in Base from a template:

class Base
{
    protected:
        virtual void function() { std::cout << "Base\n"; }
        void callfunction(void (Base::* func)()) { (this->*func)(); }

        template <typename Derived, std::enable_if_t<std::is_convertible_v<Derived const*, Base const*>, int> = 0>
        void callfunction(void (Derived::* func)()) { callfunction(static_cast<void (Base::*)()>(func)); }
};

class Derived1 : public Base
{
    public:
        void publicCall() { callfunction(&Derived1::function); }
    private:
        void function() override { std::cout << "Derived1\n"; }
};

Compiler Explorer link: https://godbolt.org/z/3x8qPK4Tf

Justin
  • 24,288
  • 12
  • 92
  • 142
  • I would be willing to do this. However, this template function gets called about 100 times by 10 classes making it very tedious include the cast 1000 times. Therefore, using casting seems quite complicated. – cwbusacker Apr 19 '21 at 21:26
  • 2
    This is allowed, but if you accidentally try this cast with a non-virtual function, you get UB. – n. m. could be an AI Apr 19 '21 at 21:42
  • I appreciate the Compiler Explorer. This is probably the one of the best answers so far. Only trouble I'm having is sometimes I want to send nullptr or NULL as the function pointer because there are cases where I don't want that function to happen in the base class. I want it to skip over it. Is there a solution to that? I'm having issue issues where it can't convert the nullptr to Derived::* @Justin – cwbusacker Apr 20 '21 at 00:39
  • @cwbusacker It seems to work for me: https://godbolt.org/z/hY1KhdhMP . Are you just asking how to test a PMF for null? That's the same as other pointers; either `if (func) /* call it */` or `if (func != nullptr) /* call it */` – Justin Apr 20 '21 at 02:21
  • @Justin, that works for the base class case. However, in my scenario (tried to simplify my question), I'm actually sending multiple function pointers to the function where some of them may be nullptr and others are not. I don't think nullptr can pass through as a Derived::*. Would the best solution be to maybe make multiple instances of that where a nullptr would be allowed? Or could I somehow change the std::is_convertible_v()? – cwbusacker Apr 20 '21 at 12:30
  • @cwbusacker `nullptr` absolutely can be passed through as a `void (Derived::*)()`: https://godbolt.org/z/Ma51cdqnn – Justin Apr 20 '21 at 16:48
  • @Justin I got this to work. Thanks for your post. One question: does the ```template , int> = 0>``` make it so that the program won't compile if somehow a non-derived class function pointer was sent in? – cwbusacker Apr 26 '21 at 22:27
  • @cwbusacker Yes, `std::is_convertible_v` is testing whether `Derived` is publicly derived from `Base` (if you want private/protected derivation, use `std::is_base_of_v`. So the `std::enable_if_t` is telling the compiler to enable this overload only if `Derived` is publicly derived from `Base`, i.e., "Only allow this function to be called when `Derived` actually derives from `Base`" – Justin Apr 26 '21 at 22:32