0

I am trying to write this function:

void CChristianLifeMinistryEditorDlg::PerformAutoAssignForAssignment(
    const MSAToolsLibrary::AssignmentType eAssignType, 
    const CString strStartingName, 
    std::function<void(CString)> funcSetAssignName)
{
    CString strName = L"abc";
    CChristianLifeMinistryEntry *pEntry = xxx;
    // ...
    pEntry->funcSetAssignName(strName);
}

I am having difficulty in passing a function to it:

PerformAutoAssignForAssignment(MSAToolsLibrary::AssignmentType_Host, L"Name 1" );
PerformAutoAssignForAssignment(MSAToolsLibrary::AssignmentType_CoHost, L"Name 2");
PerformAutoAssignForAssignment(MSAToolsLibrary::AssignmentType_ConductorCBS, L"Name 3");
PerformAutoAssignForAssignment(MSAToolsLibrary::AssignmentType_ReaderCBS, L"Name 4");

At the moment I am not passing a function:

void CChristianLifeMinistryEditorDlg::PerformAutoAssignForAssignment(
    const MSAToolsLibrary::AssignmentType eAssignType, 
    const CString strStartingName)
{
    CString strName = L"abc";
    CChristianLifeMinistryEntry *pEntry = xxx;
    // ...
    pEntry->funcSetAssignName(strName);

    if (eAssignType == MSAToolsLibrary::AssignmentType_ConductorCBS)
    {
        pEntry->SetCBSConductor(strNameToUse);
    }
    else if (eAssignType == MSAToolsLibrary::AssignmentType_ReaderCBS)
    {
        pEntry->SetCBSReader(strNameToUse);
    }
    else if (eAssignType == MSAToolsLibrary::AssignmentType_Host)
    {
        pEntry->SetVideoHost(strNameToUse);
    }
    else if (eAssignType == MSAToolsLibrary::AssignmentType_CoHost)
    {
        pEntry->SetVideoCohost(strNameToUse);
    }

}

Can you see what I am trying to do? I had hoped to avoid the if/else ladder because eventually I will have more functions to add to the ladder for other assignments. I had hoped I could pass them as a function.

wohlstad
  • 12,661
  • 10
  • 26
  • 39
Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164
  • 1
    That's because the function is a class member. (There's a hidden 'this' parameter). To get around that you can make lambda functions that include pEntry. Or use a function pointer `void (CChristianLifeMinistryEntry:: *pfn)(CString);` – QuentinUK May 07 '22 at 13:41
  • 1
    `std::function` represents an object you can call as is with operator(). I.e. the activation should be: `funcSetAssignName(strName);` I think you should use `std::bind` to bind to your object, like here: https://stackoverflow.com/questions/7582546/using-generic-stdfunction-objects-with-member-functions-in-one-class. – wohlstad May 07 '22 at 13:44
  • @QuentinUK How can I do this lambda approach? – Andrew Truckle May 07 '22 at 13:48
  • @wohlstad I am struggline to make sense of this `std::bind` approach. Thanks for the link and I will continue to re-read. – Andrew Truckle May 07 '22 at 14:04
  • 1
    @AndrewTruckle - demonstrated the 2 ways in my answer below. – wohlstad May 07 '22 at 14:06
  • 1
    Added demonstration for pointer to member to my answer below. – wohlstad May 07 '22 at 14:33
  • 1
    @AndrewTruckle the problem has nothing to do with using a CString, rather an issue of using a callable which is a method of an object. Therefore I think you should change the title of your post. – wohlstad May 07 '22 at 14:39
  • @wohlstad I am happy for you to re-phrase the question if you think it warranted. – Andrew Truckle May 07 '22 at 14:40
  • 1
    @AndrewTruckle I modified the title. Feel free to adjust if I missed anything you meant to convey. – wohlstad May 07 '22 at 14:46

1 Answers1

2

In order make a method of a class callable, you must supply a this object. Wrapping such a method in a std::function can be done in 2 ways. Both of them associate a specific class instance with a method, making it callable:

  1. Use std::bind - see the documentation: std::bind, and a specific stackoverflow post regarding this issue: Using generic std::function objects with member functions in one class.

  2. Use lambda functions, that keep the this context as a data member of the lambda object. See some general info here: What is a lambda expression in C++11?.

The code below demonstrates this 2 ways. I used std::string instead of MFC's CString, but the principle is the same.

#include <iostream>
#include <string>
#include <functional>

struct A
{
    void Do1(std::string const & str) { std::cout << "A::Do1" << std::endl; };
    void Do2(std::string const & str) { std::cout << "A::Do2" << std::endl; };
    void Do3(std::string const & str) { std::cout << "A::Do3" << std::endl; };
};


struct Dlg
{
    A a;

    void Perform(std::string const & str, std::function<void(std::string)> funcSetAssignName)
    {
        funcSetAssignName(str);
    }

    void m()
    {
        std::string str = "abc";

        // Pass method as std::function using std::bind:
        Perform(str, std::bind(&A::Do1, a, std::placeholders::_1));
        Perform(str, std::bind(&A::Do2, a, std::placeholders::_1));
        Perform(str, std::bind(&A::Do3, a, std::placeholders::_1));

        // Pass method as std::function using lambdas:
        Perform(str, [this](std::string const & str) { this->a.Do1(str); });
        Perform(str, [this](std::string const & str) { this->a.Do2(str); });
        Perform(str, [this](std::string const & str) { this->a.Do3(str); });
    }
};

int main()
{
    Dlg dlg;
    dlg.m();
    return 0;
}

The output:

A::Do1
A::Do2
A::Do3
A::Do1
A::Do2
A::Do3

UPDATE:
Following the OP's question in the comment below:
In order to let you determine the instance to apply the method inside Perform, you can use pointer to method. The downside is that the method you pass must be of a specified class (A in my example below). I.e. you loose the ability to pass callables from various classes (or global).

See the code example below:

#include <iostream>
#include <string>

struct A
{
    void Do1(std::string const & str) { std::cout << "A::Do1" << std::endl; };
    void Do2(std::string const & str) { std::cout << "A::Do2" << std::endl; };
    void Do3(std::string const & str) { std::cout << "A::Do3" << std::endl; };
};


struct Dlg
{
    A a;

    void Perform(std::string const & str, void (A::*funcSetAssignName)(std::string const & str))
    {
        // Here the instance for class A can be determined (I simply used `a`):
        (a.*funcSetAssignName)(str);
    }

    void m()
    {
        std::string str = "abc";

        // Pass pointer to method:
        Perform(str, &A::Do1);
        Perform(str, &A::Do2);
        Perform(str, &A::Do3);
    }
};

int main()
{
    Dlg dlg;
    dlg.m();
    return 0;
}

You can see some more examples here: Calling C++ member functions via a function pointer.

wohlstad
  • 12,661
  • 10
  • 26
  • 39
  • Thanks, I appreciate the examples. However, the `pEntry` is determined inside the `Perform` function itself, whereas you seem to have a globally visible instance inside your class. Does this cause a problem? – Andrew Truckle May 07 '22 at 14:10
  • It's actually a problem. Because pEntry is your `this`, which has to be binded when the std::function is created when calling `Perform`. Maybe `Perform` can accept a pointer to method instead of `std::function` ? – wohlstad May 07 '22 at 14:13
  • I saw the original comment about a pointer function `void (CChristianLifeMinistryEntry::* pfn)(CString)` but passing that and then trying to call it `pfn(strNameToUse)` doesn't work nor `pEntry->pfn(...)`. – Andrew Truckle May 07 '22 at 14:18
  • 1
    I will update my answer soon. – wohlstad May 07 '22 at 14:18
  • I have been able to re-produce the member function approach. All of the methods are inside the one class so it is all good. Thanks again for the examples and explanations. – Andrew Truckle May 07 '22 at 14:41