This is a poll for opinions on the most readable way to do something -- whether to use a C++ pointer-to-member, a byte offset, or a templatized functor to define "select member X from structure foo".
I've got a type that contains a large vector of structures, and I'm writing a utility function that basically operates as a reduce over some range of them. Each structure associates a group of dependent variables with some point along an independent dimension -- to invent a simplified example, imagine that this records a series 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 a cubic interpolation to guess those conditions for some given point of time in 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'd like to generalize this function so that it can be applied generically to any member, not just temperature. I can think of three ways to do this, and while they're all straightforward to code, I'm not sure what will be the most readable to whoever has to use this a year from now. Here's what I'm considering:
Pointer-to-member 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 feels like the most "C++y" way to do it, but it looks strange, and the whole pointer-to-member syntax is rarely used and therefore poorly understood by most people on my team. It's the technically "right" way, but also the one I'll get the most confused emails about.
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 does the pointer math explicitly. This approach will be immediately familiar and understandable to everyone on my team (who all learned C before C++), and it is robust, but it just seems icky.
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 most straightforward and STL-ish way of doing things, but it seems like a whole bunch of extra typing and syntax and extemporaneous class definitions. It compiles to almost exactly the same thing as the two above, but it also dumps a bunch of redundant function definitions all over the executable. (I've verified this with /FAcs, but maybe the linker takes them out again.)
All the three above will work, and the compiler emits pretty much the same code for all of them; so, the most important choice I have to make is simply which is most readable. What do you think?