4

I know how to make a function an input argument when the function isn't a method, but I can't figure out how to do it when the function is a method.

Here's what I tried:

#include <iostream>

class A
{
    void funct_1(int a, void (A::*f)(int))
    {
        (A::*f)(a);
    }

public:

    void funct_2(int k)
    {
        // do something
    }

    void funct_3(int k)
    {
        // do something
    }

    void funct_4(int k)
    {
        // some reason to use the function argument functionality...
        if (k % 2)
            funct_1(k, funct_2);
        else
            funct_1(k, funct_3);
    }
};

int main()
{

    A a;

    a.funct_4(4);

    return 0;
}

The above code doesn't compile. I've tried lots of variations, but can't find any syntax use that works. Could someone explain what I'm doing wrong here in terms of the syntax?

Elliott
  • 2,603
  • 2
  • 18
  • 35

2 Answers2

3

The pointer to member syntax is always hard to figure out. You are always there, just change the first member function to

void funct_1(int a, void (A::*f)(int))
{
    (this->*f)(a);
}

and the fourth to

void funct_4(int k)
{
    if (k % 2)
        funct_1(k, &A::funct_2);
    else
        funct_1(k, &A::funct_3);
}

For you original attempt to call the member function, note that (A::*f)(a); is conceptually wrong, as it doesn't associate the member function pointer f with an instance of A. Member function pointers are offsets into a class definition, but not tight to a particular instance of that class. This must be provided when doing the actual call. Specific operator exist to do exactly that, i.e. .* and ->* as in this->*f. As their precedence is low, you need to add an additional set of parentheses: (this->*f).

lubgr
  • 37,368
  • 3
  • 66
  • 117
  • 2
    Note that this can lead to unexpected behavior, OP should pass an instance of `A` as argument aswell. – Timo May 20 '19 at 09:30
  • 1
    @Timo Would it be a reasonable scenario that the `A` instance is not `this`? I don't think the OP wants to consider this case, but I might be wrong. – lubgr May 20 '19 at 09:31
  • Idk either but as soon as he uses a 2nd instance of `A` in one of the calling functions he will probably run into trouble. – Timo May 20 '19 at 09:35
  • @Timo Not sure if I get that, you mean creating an `A` instance in one of the member functions `funct_2` or `funct_3`? – lubgr May 20 '19 at 09:42
  • In `funct_4`. Ex if he wants to call `funct_2` or `funct_3` on a different instance. The reason why I mention it is because when passing member function pointers you usually want to pass the object to call that function on along aswell. – Timo May 20 '19 at 09:46
  • 1
    @Timo I see. But when the pointer to member is passed within the class itself, implicitly assuming that `this` is the instance to use when invoking the pointee seems fine to me. But I agree that it's usually a good idea to think of pointer-to-member as something the actual instance should be passed alongside. – lubgr May 20 '19 at 09:58
3

I don't know if you need to use an exact signature, but can't you just do this?

template<typename Func>
void funct_1(int a, Func&& func)
{
   std::forward<Func>(func)(a);
}

the signature of your method does not change:

void funct_4(int k)
{
    // some reason to use the function argument functionality...
    if (k % 2)
        funct_1(k, funct_2);
    else
        funct_1(k, funct_3);
}

you can even extends it for any arbitrary number of parameters

template<typename Func>
void funct_1(Func&& func, Args&&... args)
{
   std::forward<Func>(func)(std::forward<Args>(args)...);
}

Those allow to use function as parameter. If you intend to use it inside your class in the way you've showed it's mostly a design-flaw and re-think the class structure.

If you want to pass around member function inside your class enclose it in a lambda and std::function, so you can use it as variables and use the method above.

If you just want a clean way for your example, actually do exists an extremely way:

void funct_1(int a, auto&& func)
{
   (this->*func)(a);
}

So your sample become

#include <iostream>
#include <functional>

class A
{
    void funct_1(int a, auto&& func)
    {
       (this->*func)(a);
    }
    
public:
    void funct_2(int k)
    {
        std::cout << "func 2" << std::endl;
    }

    void funct_3(int k)
    {
        std::cout << "func 3" << std::endl;
    }

    void funct_4(int k)
    {
        // some reason to use the function argument functionality...
        if (k % 2)
            funct_1(k, &A::funct_2);
        else
            funct_1(k, &A::funct_3);
    }
};

int main()
{
    A a;
    a.funct_4(4);

    return 0;
}

Live demo

Moia
  • 2,216
  • 1
  • 12
  • 34
  • 1
    Why the `std::forward`? – Timo May 20 '19 at 09:37
  • @Timo perfect forwarding of universal reference. https://eli.thegreenplace.net/2014/perfect-forwarding-and-universal-references-in-c – Moia May 20 '19 at 09:38
  • 1
    Yeah but why do you forward the callable? – Timo May 20 '19 at 09:39
  • 1
    @Timo https://stackoverflow.com/questions/36920485/purpose-of-perfect-forwarding-for-callable-argument-in-invocation-expression but you have a point if you mean it could be not strictly necessary in this example – Moia May 20 '19 at 09:42
  • 1
    @Elliott Smith a template avoid you a painful pointer syntax and allow you to be more flexibile. What if at a certain moment want to accept a different signature (both in return or input type?) – Moia May 20 '19 at 09:45
  • Actually, looking at this a year later, I can't get anything quite so clean at the call site. Copy-pasting the code here gives me an error: `invalid use of non-static member function`. This is the error for gcc, but it's the same problem for clang, which is a shame. I'd like the call sites to look nicer. – Elliott Sep 23 '20 at 07:31
  • @Elliott well this is kind of vague. Which code and which parts gives you the error... – Moia Sep 23 '20 at 08:36
  • @Moia, the call site: `invalid use of non-static member function ‘void A::funct_2(int)’ 27 | funct_1(k, funct_2);`, and also the same for the other function call: `funct_1(k, funct_3);` – Elliott Sep 23 '20 at 09:04
  • @Moia, I'd agree this is a rare problem. I'm actually only revising this because I'm answering someone else's question and wanted the cleanest way to do this part. I think that it'd be good for you to edit your solution to be like that in your code snippet. – Elliott Sep 23 '20 at 09:18
  • 1
    @Elliott as you said looking at it a year later, do exists a much cleaner way to express it, if at least C++14 is enabled. You're going to find it in the edited answer – Moia Sep 23 '20 at 09:35