Search code examples
c++templatesclass-members

template to access different members of objects passed as arguments


I have a function to compute gradient of different variable defined on set of neighbor points. The algorithm is always the same, but depending on what is computed, different member data of the neighbors are accessed, e.g. when computing gradient of velocity, use Node::velocity, when computing gradient of stress, use Node::stress. What is the best way to avoid writing the same function several times?

I had several possibilities in mind:

  1. Pass lambda function (c++0x) or callable object returning that particular member data in question, called like

    gradVelocity=computeGradient(listOfNeighbors,[](const Node& n){ return n.velocity; });
    

    The minus is extra function call at every read.

  2. Template the function based on integer saying what is being computed:

    enum{VAL_VELOCITY=0,VAL_STRESS,VAL_SOMETHING};
    template<int what> computeGradient(const std::list<Node>& neighbors){
        /*loop over neighbors*/
             value=(what==VAL_VELOCITY?neighbor.velocity:((what==VAL_STRESS)?neighbor.stress:neighbor.something);
        /* and so on */
    }
    
    /* called like this */
    gradVelocity=computeGradient<VAL_VELOCITY>(neighbors);
    

    It should be perhaps efficient (hoping compiler will optimize the conditional with constants away in individual instantiations), but readability and maintainability is pretty low.

Some better idea?


Solution

  • Pointer to member is what you need. The type is written as T S::* T is the type of the data member, S is your struct or class. Here is a small example:

    #include <iostream>
    
    struct Foo
    {
      int a;
      double b;
    
      Foo(int a, double b)
        : a(a), b(b)
      { }
    };
    
    template<typename T, T Foo::* mem>
    void print(const Foo& foo)
    {
      std::cout << foo.*mem << std::endl;
    }
    
    int main()
    {
      Foo f(5, 3.14);
      print<int, &Foo::a>(f);
      print<double, &Foo::b>(f);
    }