7

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)

JeJo
  • 30,635
  • 6
  • 49
  • 88
Fractale
  • 1,503
  • 3
  • 19
  • 34

2 Answers2

7

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).

lubgr
  • 37,368
  • 3
  • 66
  • 117
  • BTW, I don't see how there is a factorization between `f(someStream, &T::getData1); f(someStream, &T::m_data1);` and `someStream << Myglobal->getData1(); someStream << Myglobal->m_data1;` (which can even be `someStream << Myglobal->getData1() << Myglobal->m_data1;`) ;-) – Jarod42 Oct 17 '19 at 09:42
  • 2
    @Jarod42 Well... fair point :) `f(someStream, &T::getData1); f(someStream, &T::m_data1);` is 57 characters, and so is `someStream << Myglobal->getData1() << Myglobal->m_data1;`. At least this solution doesn't require more typing. But now when you want to `std::format` before `<<`-ing it to the stream... – lubgr Oct 17 '19 at 09:47
6

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);
JeJo
  • 30,635
  • 6
  • 49
  • 88
  • 2
    This is awesome! One might only argue about the readability though. Buy once in pointer-to-member-land, readability isn't necessarily the ultimate goad anyway. – lubgr Oct 17 '19 at 11:35
  • @lubgr Yeah. However, the `std::invoke` nowadays saves a lot, especially from the era of `(obj->*mem_function)(arg1, arg2,...)` to simply more readable `std::invoke(mem_function, obj, std::forward(args)...)` – JeJo Oct 17 '19 at 16:50