Is there any way to create a function that takes as argument a member function or a member?

I have a function like this:

void f(std::ofstream& ostrm)
{
    auto a = Myglobal->getData1();
    ostrm << a;

    auto b = Myglobal->getData2();
    ostrm << b;

    auto c = Myglobal->m_data1;
    ostrm << c;

    auto d = Myglobal->m_data2;
    ostrm << d;

    //...
    auto z = Myglobal->getData1000();
    ostrm << z;
}

Is there any way to create a function that takes as argument a member function or a member to factorize this code?

(a, b, c, d and z are not the same type)

2 answers

  • answered 2019-10-17 07:49 lubgr

    Yes, there is. One way is to turn void f into a function template, then pass the pointer to member of the desired data member or member function and let std::invoke (C++17, <functional> header) do the rest:

    template <class PtrToMember>
    void f(std::ofstream &ostrm, PtrToMember m){
        ostrm << std::invoke(m, Myglobal);
    }
    
    // call like this:
    
    f(someStream, &T::getData1);
    f(someStream, &T::m_data1);
    

    where you should replace T by the the type of Myglobal of course. The nice thing about std::invoke is that it automatically handles all member (data or functions).

  • answered 2019-10-17 10:36 JeJo

    The @lubgr has explained the use of std::invoke. One step further you can reduce the entire lines of code to a single line, using fold expression from .

    template<typename... Mems>
    void f(std::ofstream& ostrm, Mems&&... args)
    {
        ((ostrm << std::invoke(args, Myglobal) << " "), ...);
    }
    

    and you will pass the desired members or member functions to the function at once, instead of calling many times.

    f(obj,
        &MyClass::m_data1, &MyClass::m_data2, &MyClass::m_data3,
        &MyClass::getData1, &MyClass::getData2, &MyClass::getData3);
    

    (See live example)


    And providing one more template parameter in the function f(for the Class), you can make it completely generic code and no global variables needed.

    template<typename Class, typename... Mems>
    void f(std::ofstream& ostrm, const Class& obj, Mems&&... args)
    //                           ^^^^^^^^^^^^^^^^
    {
        ((ostrm << std::invoke(args, obj) << " "), ...);
    }
    

    and now in the main()

    std::ofstream ostrm{"test_file.txt"};
    const auto obj{ std::make_unique<MyClass>() };
    f(ostrm,
        obj,
        &MyClass::m_data1, &MyClass::m_data2, &MyClass::m_data3,
        &MyClass::getData1, &MyClass::getData2, &MyClass::getData3);