1

We're transitioning to C++11 but still a few months away. Please feel free to give C++11 responses, but we're curious whether there's a non-ugly way to do it in C++98/03.


A co-worker came to me with code resembling:

typedef void(B::*fptr_t)();

void A::my_foreach( uint idx, fptr_t fptr ) {
  for( iter_type iter = asdf[idx].begin(); iter != asdf[idx].end(); ++iter )
    iter->second->*fptr();
}

void A::get( uint idx ) { my_foreach( idx, &B::get ); }
void A::get( uint idx ) { my_foreach( idx, &B::get ); }

... asking if there was a better way, to which I responded with:

void A::get( uint idx ) {
  std::for_each( asdf[idx].begin()
               , asdf[idx].end()
               , boost::bind( &B::get, _1 ) );
}
void A::put( uint idx ) {
  std::for_each( asdf[idx].begin()
               , asdf[idx].end()
               , boost::bind( &B::put, _1 ) );
}

... but that still left me feeling like more could be done to avoid code duplication. The general solutions that have occurred to me are:

  1. Re-factor the code so the involved classes have less responsibilities, and wrap the container(s) in decorator/proxy objects that provide some of the needed functionality.
  2. Generic apply to my container member function that takes the intended function as an argument (that is, callers must provide the function)
  3. Make two private member functions: the function mentioned in (2) and one to return a functor to call it, then public functor objects initialized in the constructor.
  4. Use boost::bind to wrap the whole thing, making a functor that could be used like this:

.

functor_t get = my_binder( &B::get );
functor_t put = my_binder( &B::put );

... where my_binder would produce an object that can perform the same operations, but that quickly got ugly because of how many functions are actually executing in the above code (operator[], X::begin, X::end, something for applying the function ptr to each element of the container).

Is there another/better way prior to C++11, or are those solutions pretty much what's available?

Praetorian
  • 106,671
  • 19
  • 240
  • 328
Brian Vandenberg
  • 4,011
  • 2
  • 37
  • 53
  • You can replace `boost::bind( &B::get, _1 )` with `std::mem_fun(&B::get)`, and then write a function that takes a `std::mem_fun_t` – Praetorian Sep 29 '17 at 21:37

1 Answers1

1

std::mem_fn is the binder from step 4.

You didn't quite mention the fact that your container looks to be associative (e.g. std::map). I'd suggest using Boost Range for adapting ranges to project to the mapped values:

Live On Coliru

#include <boost/range/algorithm.hpp>
#include <boost/range/adaptors.hpp>

using namespace boost::adaptors;

template<typename T> struct Container {
    template <typename F> void my_foreach(uint idx, F&& f) {
        boost::for_each(_data[idx] | map_values, std::forward<F>(f));
    }

    std::map<int, std::map<int, T> > _data;
};

#include <iostream>
struct B {
    B(std::string s) : _s(s) {}
    void foo()      const { std::cout << "foo(" << _s << ")\n"; }
    void bar(int i) const { std::cout << "bar(" << _s << ", " << i << ")\n"; }
  private:
    std::string _s;
};

#include <functional>
int main() {
    Container<B> c;
    c._data = { // just some demo data
        { 1, { { 100, {"hundred"} }, { 1000, {"thousand"} }, { 1000000, {"million"} }, } },
        { 2, { { 100, {"hundert"} }, { 1000, {"tausend"} }, { 1000000, {"miljon"} }, } },
        { 3, { { 100, {"cent"} }, { 1000, {"mille"} }, { 1000000, {"million"} }, } },
        { 4, { { 100, {"honderd"} }, { 1000, {"duizen"} }, { 1000000, {"miljoen"} }, } },
    };

    c.my_foreach(3, std::mem_fn(&B::foo));
    c.my_foreach(1, [](B const& b) { b.bar(42); });
}

Printing:

foo(cent)
foo(mille)
foo(million)
bar(hundred, 42)
bar(thousand, 42)
bar(million, 42)
sehe
  • 374,641
  • 47
  • 450
  • 633
  • In the actual code I think it's `vector >`, but the question at this point is more academic so I left out the exact container type because I'm more interested in learning something new than getting a solution to that particular problem (he's using it with duplicated code to avoid spending a lot of time researching this). – Brian Vandenberg Oct 01 '17 at 07:51
  • Well, your code has `iter->second` so it only makes sense if `TheType` happens to be like `std::pair`. You can still use the `map_values` adaptor then (I think). If all that was irrelevant, then perhaps `std::mem_fn` was the answer indeed. – sehe Oct 01 '17 at 16:33
  • Good point, I hadn't really been thinking too deeply about the containers used. I'll have to tinker with mem_fn & get a better idea of what it does. – Brian Vandenberg Oct 02 '17 at 05:42