1

Needing several variations of a simple modal dialog with an MFC project, I wrote a simple class, CDialogDlg, which extends the standard MFC CDialog class and contains hooks for implementation specific callbacks that are normally implemented as methods in the class extending CDialog. These callbacks, if provided, are used when processing some events such as dialog initialization, OK button processing, and the DoDataExchange() function which provides the method for interfacing the dialog class variables and the actual dialog controls.

My first version of this class used standard functions for the callbacks. So the callback setting methods of the extended class set a function pointer with the address of the standard function.

void SetDataExchangeCallBack(void(*f)(CDataExchange* pDX)) { funcDX = f; };
void SetInitCallBack(void(*f)(CWnd *dlgWnd)) { funcInit = f; }
void SetOnOkCallBack(void(*f)(CWnd *dlgWnd, CDocument *pDoc)) { funcOK = f; }

Then I realized I should be able to use a lambda instead of a standard function. The first version of the lambdas, which used the same parameters as the standard functions, that did not capture any variables compiled fine and worked fine with the existing methods and function pointers in the extended class.

However when I tried to capture a variable in the lambda specified in the callback setter method, I had compile errors.

Reviewing Passing capturing lambda as function pointer and C++ lambda with captures as a function pointer I get that trying to do a lambda with capture will not compile with the definitions of function pointer and callback setting function that I am using.

So I decided that I should add an additional lambda specific function pointer within the class along with an override of the existing methods with an additional method setting the callbacks with the lambda specific function pointer within the class.

Question: What should the function pointer definition and the parameter definition in the function that sets the callback for accepting a lambda with captured variables look like? I would like to capture the local variable CPCSampleDoc *pDoc in the method CPCSampleView::OnDisplayReportList (). This variable contains a pointer to the CDocument derived object for the view that I would like to capture rather than using the method of storing it and doing the static_cast to get it back in the current version of the lambdas that do not capture.

Source Code

The CDialogDlg class which extends CDialog looks like the following:

class CDialogDlg : public CDialog
{
    // Construction
    void(*funcDX)(CDataExchange* pDX);
    void(*funcDXpDoc)(CDataExchange* pDX, CDocument *pDoc);
    void(*funcInit)(CWnd *dlgWnd);
    void(*funcOK)(CWnd *dlgWnd, CDocument *pDoc);
    CDocument *m_pDoc;

public:
    CDialogDlg(UINT nIDTemplate, CWnd* pParentWnd = NULL, void(*f)(CDataExchange* pDX) = NULL) : CDialog(nIDTemplate, pParentWnd) { funcDX = f; funcInit = NULL; }
    CDialogDlg(UINT nIDTemplate, CDocument *pDoc, CWnd* pParentWnd = NULL, void(*f)(CDataExchange* pDX, CDocument *pDoc) = NULL) : CDialog(nIDTemplate, pParentWnd) {
        funcDX = NULL; funcDXpDoc = f; funcInit = NULL; m_pDoc = pDoc;
    }

    void SetDataExchangeCallBack(void(*f)(CDataExchange* pDX)) { funcDX = f; };
    void SetInitCallBack(void(*f)(CWnd *dlgWnd)) { funcInit = f; }
    void SetOnOkCallBack(void(*f)(CWnd *dlgWnd, CDocument *pDoc)) { funcOK = f; }

    // Dialog Data
    //{{AFX_DATA(CCashierNoDlg)
    //}}AFX_DATA

    // Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CCashierNoDlg)
protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
    //}}AFX_VIRTUAL

protected:
    // Generated message map functions
    //{{AFX_MSG(CCashierNoDlg)
    virtual BOOL OnInitDialog();
    virtual void OnOK();
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

BEGIN_MESSAGE_MAP(CDialogDlg, CDialog)
    //{{AFX_MSG_MAP(CCashierNoDlg)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CDialogDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    if (funcDX) funcDX(pDX);
    if (funcDXpDoc) funcDXpDoc(pDX, m_pDoc);
    //{{AFX_DATA_MAP(CCashierNoDlg)
    //}}AFX_DATA_MAP
}

BOOL CDialogDlg::OnInitDialog()
{
    CDialog::OnInitDialog();
    if (funcInit) funcInit(this);
    return TRUE;  // return TRUE unless you set the focus to a control
                  // EXCEPTION: OCX Property Pages should return FALSE
}

void CDialogDlg::OnOK()
{
    if (funcOK) funcOK(this, m_pDoc);
    CDialog::OnOK();
}

The CView method which is triggered by a menu selection displays a dialog with a list of items to choose from. The dialog presented is a CDialogDlg object with a specific dialog template ID. For special processing, I am using two different callbacks which are currently using a lambda that does not capture. The result is the following:

void CPCSampleView::OnDisplayReportList ()
{
    CPCSampleDoc *pDoc = GetDocument();

    CDialogDlg myDialog(IDD_DIALOG_REPORTLIST, pDoc, this, [](CDataExchange* pDX, CDocument *pDoc) {
        if (pDX->m_bSaveAndValidate) {
        }
        else {
            CPCSampleDoc *pDocDoc = static_cast<CPCSampleDoc *>(pDoc);
            POSITION pos = NULL;

            do {
                CPCSampleDoc::ListReportList sectionHeader;
                CListBox *x = static_cast<CListBox *>(pDX->m_pDlgWnd->GetDlgItem(IDC_LIST1));

                pos = pDocDoc->GetReportSectionHeader(pos, sectionHeader);
                x->AddString(sectionHeader.m_SectionTitle);
            } while (pos);
        }
    });

    myDialog.SetOnOkCallBack([](CWnd *dlgWnd, CDocument *pDoc) {
        CPCSampleDoc *pDocDoc = static_cast<CPCSampleDoc *>(pDoc);
        CListBox *x = static_cast<CListBox *>(dlgWnd->GetDlgItem(IDC_LIST1));
        int iPtr = x->GetCurSel();
        POSITION pos = NULL;

        do {
            CPCSampleDoc::ListReportList sectionHeader;

            pos = pDocDoc->GetReportSectionHeader(pos, sectionHeader);
            if (iPtr < 1) {
                pDocDoc->MoveToReportSectionHeader(sectionHeader.m_ListOffset);
                break;
            }
            iPtr--;
        } while (pos);
    });

    myDialog.DoModal();
}
Richard Chambers
  • 16,643
  • 4
  • 81
  • 106

1 Answers1

2

You might use std::function to allow capturing lambdas and other functors and regular function pointers:

void(*funcDX)(CDataExchange* pDX);
void(*funcDXpDoc)(CDataExchange* pDX, CDocument *pDoc);
void(*funcInit)(CWnd *dlgWnd);
void(*funcOK)(CWnd *dlgWnd, CDocument *pDoc);

becomes

std::function<void(CDataExchange*)> funcDX;
std::function<void(CDataExchange*, CDocument*)> funcDXpDoc;
std::function<void(CWnd*)> funcInit;
std::function<void(CWnd*, CDocument*)> funcOK;

You setter/constructor should change pointer function to std::function too:

void SetInitCallBack(std::function<void(CWnd*)> f) { funcInit = f; }

Else your usage is identical:

if (funcDX) funcDX(pDX);

or

funcInit = nullptr;
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Thank you. I will try this out later this morning when I get back into the lab and follow up with how it turns out or if I have any questions. Its 1am here on the US East Coast and I need to get a few hours sleep. – Richard Chambers Sep 12 '19 at 05:13
  • The approach I am using at the moment is to keep what was there and provide a clone of the three callback setter functions that take `std::function<>` arguments with the necessary additional `std::function<>` variables and the addition of a check in the class methods that use the callbacks if they are set to something other than `nullptr`. My understanding is that using `std::function<>` wraps the lambda in such a fashion that different lambdas that capture different variables can all use the same callback variable so long as the lambda arguments are the same. Is that correct? – Richard Chambers Sep 12 '19 at 15:29
  • You can fully replace pointer function by `std::function`. `std::function` has constructor that take pointer function. `std::function` can store mostly any callable if parameters matches. – Jarod42 Sep 12 '19 at 16:16