The easiest way to code structarray map functor in C ++

This is a poll for opinions on the most readable way to do something: whether to use a C ++ pointer, a byte offset, or a template functor to determine "select an X element from the foo structure".

I have a type that contains a large vector of structures, and I'm writing a utility function that basically works how to reduce over a certain range of them. Each structure associates a group of dependent variables with a certain point along an independent measurement - in order to come up with a simplified example, imagine that it captures a number of environmental conditions for a room over time:

// all examples are psuedocode for brevity struct TricorderReadings { float time; // independent variable float tempurature; float lightlevel; float windspeed; // etc for about twenty other kinds of data... } 

My function simply performs cubic interpolation to guess these conditions for some given point in time between the available samples.

 // performs Hermite interpolation between the four samples closest to given time float TempuratureAtTime( float time, sorted_vector<TricorderReadings> &data) { // assume all the proper bounds checking, etc. is in place int idx = FindClosestSampleBefore( time, data ); return CubicInterp( time, data[idx-1].time, data[idx-1].tempurature, data[idx+0].time, data[idx+0].tempurature, data[idx+1].time, data[idx+1].tempurature, data[idx+2].time, data[idx+2].tempurature ); } 

I would like to generalize this function so that it can be applied in the general case to any term, and not just to temperature. I can come up with three ways to do this, and although they are all easy to code, I’m not sure what will be the most readable for anyone who should use it in a year. Here is what I am considering:


Member Pointer Syntax

 typedef int TricorderReadings::* selector; float ReadingAtTime( time, svec<TricorderReadings> &data, selector whichmember ) { int idx = FindClosestSampleBefore( time, data ); return CubicInterp( time, data[idx-1].time, data[idx-1].*whichmember, /* ...etc */ ); } // called like: ReadingAtTime( 12.6f, data, &TricorderReadings::windspeed ); 

This seems like the most “C ++ y” way to do this, but it looks weird, and the whole pointer-member syntax is rarely used and therefore is poorly understood by most people on my team. This is technically the “right” way, but also the one for which I receive the most vague letters.

Structure offset

 float ReadingAtTime( time, svec<TricorderReadings> &data, int memberoffset ) { int idx = FindClosestSampleBefore( time, data ); return CubicInterp( time, data[idx-1].time, *(float *) ( ((char *)(&data[idx-1]))+memberoffset ), /* ...etc */ ); } // called like: ReadingAtTime( 12.6f, data, offsetof(TricorderReadings, windspeed) ); 

This is functionally identical to the above, but the math of the pointer is explicitly specified. This approach will be immediately familiar and understandable to everyone in my team (who has learned everything from C to C ++), and it is strong, but it just seems not good.

Templatized functor

 template <class F> float ReadingAtTime( time, svec<TricorderReadings> &data ) { int idx = FindClosestSampleBefore( time, data ); return CubicInterp( time, data[idx-1].time, F::Get(data[idx-1]) ), /* ...etc */ ); } // called with: class WindSelector { inline static float Get(const TricorderReadings &d) { return d.windspeed; } } ReadingAtTime<WindSelector>( 12.6f, data ); 

This is the easiest and STL-ish way to do something, but it looks like a whole bunch of extra typing and syntax and later class definitions. It compiles almost exactly the same as the two above, but it also unloads a bunch of redundant function definitions throughout the executable. (I checked this with / FAcs , but maybe the linker takes them out again.)


All three above will work, and the compiler emits almost the same code for all of them; so the most important choice I have to make is just what is most readable. What do you think?

+3
c ++ functor readability pointer-to-member
source share
4 answers

If your team consists of intelligent, intelligent people, I would say to trust them and their abilities, and use the technically preferred solution offered by member pointer syntax. This is what it was created for.

If you are really worried, you can take some steps to alleviate any future problems with

  • Marking the comment next to typedef and its use is called the member pointer syntax so that other team members know what to look for.
  • Indicate this explicitly in the code overview, where many of them should be present. Suggest changing it if it is deemed incomprehensible or too obscure for maintenance.

The other two approaches have problems, both described and beyond:

  • Both require more code, have more space for typos, etc.
  • The offsetof primitive offsetof limited in what types it can be applied to:

    Due to the extended functionality of C ++ structures in this language, the use of offsetof is limited to “POD types”, which for classes more or less corresponds to the concept of C structure (although non-derived classes with only public non-virtual member functions and without a constructor and / or the destructor will also qualify as a POD).

From here .

+1
source share

In this case, I find the Templatized Functor very clear.

 ReadingAtTime<WindSelector>( 12.6f, data ); 
+3
source share

More than an STL-ish method, it will be a generic functor that makes access through a member pointer look like a function call. It might look something like this:

 #include <functional> template <class T, class Result> class member_pointer_t: public std::unary_function<T, Result> { Result T::*member; public: member_pointer_t(Result T::*m): member(m) {} Result operator()(const T& o) const { return o.*member; } }; template <class T, class Result> member_pointer_t<T, Result> member_pointer(Result T::*member) { return member_pointer_t<T, Result>(member); } float ReadingAtTime( float time, const std::vector<TricorderReadings> &data, member_pointer_t<TricorderReadings, float> f ) { int idx = FindClosestSampleBefore( time, data ); return CubicInterp( time, data[idx-1].time, f(data[idx-1])); } ReadingAtTime( 12.6f, data, &TricorderReadings::windspeed); 

The example also includes a helper function that helps to derive template arguments for the functor (not used in this example).

The ReadingAtTime function can also accept a template functor:

 template <class Func> float ReadingAtTime( float time, const std::vector<TricorderReadings>& data, Func f); ReadingAtTime( 12.6f, data, member_pointer(&TricorderReadings::windspeed)); 

Thus, you can use all kinds of functions / functors to get the value from the [idx - 1] data, and not just the member pointers.

More general member_pointer equivalents can be std :: tr1 :: bind or std :: tr1 :: mem_fn.

+2
source share

For simple things, I would prefer a Pointer-to-member solution. However, there are two possible advantages to a functional approach:

  • separating the algorithm from the data allows you to use the algorithm for more things in the future, the sine it works with anything, if you can build your own functor.

  • Associated with C # 1, this can make it easier to verify the algorithm, since you are able to provide test data for a function that does not include creating complete data objects that you intend to use. You can use simpler mock objects.

However, I think that a functor approach is worth it only if the function you are performing is very complex and / or used in many different places.

+1
source share

All Articles