0

Overall design: I have an aggregate class C that contains N member variables of type M_i, i = 1 ... N that each have a common write-only update() interface as well as class-specific read-only accessor functions [F]un_i(), [F] = any letter, i = 1 .. N (they do not have such regular names in reality). Each of the member types M_i forms an independent abstraction of its own, and is used elsewhere in my program.

The aggregate class needs to update all the members in a single transaction, so it has an update() function of its own calling the update() member function of all its member variables.

// building blocks M_i, i = 1 ... N

class M_1
{
public:
    // common write-only interface
    void update();

    // M_1 specific read-only interface
    int fun_1() const;
    // ...
    int fun_K() const;

private:
    // impl
};

// ...

class M_N
{
public:
    // common write-only interface
    void update();

    // M_N specific read-only interface
    int gun_1() const;
    // ...
    int gun_K() const;

private:
    // impl
};

// aggregate containing member variables M_i, i = 1 ... N
class C
{
public:
    // update all members in a single transaction
    void update() 
    {
        m1_.update();
        // ...
        mN_.update();
    }

    // read-only interface?? see below

private:
    M_1 m1_;
    // ...
    M_N mN_;
};

Question: the do I access the various member functions of the various member variables in the aggregate class? I can think of three alternatives:

Alternative 1: write N * K delegates to all K member functions of all N member variables

class C
{
    int fun_1() const { return m1_.fun_1(); }
    // ...
    int fun_K() const { return m1_.fun_K(); }

    // ...

    int gun_1() const { return mN_.gun_1(); }
    // ...
    int gun_K() const { return mN_.gun_K(); }

    // as before
};

int res = C.fun_5(); // call 5th member function of 1st member variable 

Alternative 2: write N accessors to all N member variables

class C
{
    M_1 const& m1() const { return m1_; }

    // ...

    M_N const& mN() const { return mN_; }

    // as before
};

int res = C.m1().fun_5(); // call 5th member function of 1st member variable

Alternative 3: write 1 accessor template to all N member variables

class C
{
public:
    enum { m1, /* ... */ mN };

    template<std::size_t I>
    auto get() const -> decltype(std::get<I>(data_)) 
    { 
        return std::get<I>(data_); 
    }

private:
    std::tuple<M_1, /* ... */ M_N> data_;
};

int res = C.get<m1>().fun_5(); // call 5th member function of 1st member variable

Alternative 1 avoids violating the Law of Demeter but it needs an awful lot of tedious boiler plate code (in my application, N = 5 and K = 3, so 15 delegating wrappers). Alternative 2 cuts down on the number of wrappers, but the calling code feels a little uglier to me. But since all that code is read-only, and modfications can only happen through the consistent aggregate update(), my current opinion that Alternative 2 is preferable to Alternative 1 (and at least safe). If that's the case, then a fortiori, Alternative 3 should be the best choice since it uses only a single accessor and has the same safety guarantees as Alternative 2.

Question: what is the preferred interface for this type of code?

TemplateRex
  • 69,038
  • 19
  • 164
  • 304

4 Answers4

1

One other possibility is

int func(int i, int j); // i,j can be enums as well..

Though you need to decide if this makes sense for you. You would need to write a huge nested switch inside though, but the interface is simpler.

This method is ideal of course if you can store your objects in an array, and all member functions are part of a common interface of M_i types.

Karthik T
  • 31,456
  • 5
  • 68
  • 87
  • Unfortunately, the names of the member functions are not regularly named `fun_ij` but differ between the `M_i` types. What I could try is to store the `M_i` inside a `std::tuple` and at least use `get` with an `enum` with named constants internally. But that doesn't really solve the multiplicity problem of Alternative 1. And then I still need to solve the 2nd layer of different member functions per type. – TemplateRex Feb 18 '13 at 09:30
  • @rhalbersma if my solution with `enum` isnt possible, and if common interface isnt possible, then your only good choice is your alternative #1 with perhaps some macros to reduce repetition. – Karthik T Feb 18 '13 at 09:32
  • Could you explain why you would prefer #1 to #2? Class `C` is an aggregate for reading, and only provides an abstraction `update()` for writing. Why is exposing read-only references to its building blocks so bad? – TemplateRex Feb 18 '13 at 09:42
  • @rhalbersma you are right, choice between they two might be a matter of style and number of objects vs number of functions vs number of calls – Karthik T Feb 18 '13 at 09:45
  • I have updated my question with a 3rd alternative using a `std::tuple` and `std::get` template accessor that can be called with named constants stored in an `enum`. – TemplateRex Feb 18 '13 at 09:58
1

The solution that gives you the best user friendly code with compile time resolution of calls has to rely on templates.

Indeed, if you want to be able to call fun(i,j) (actually fun<i,j>()) where i is an index to a member variable, and j an index to a member function of this variable, then you have to define the mappings. Both mappings.

  • First mapping between the member variable index and the variable itself, that implies a mapping between the member variable index and the variable type.

  • Second mapping between the member function index and the member function itself. However, as this mapping depend on the type of the indexed member variable, it has to be defined for every combination. You cannot provide the user a completely indexed solution without defining this mapping. Or the other way round: if you don't want the caller to bother about the type of the i-th variable to know what is the name of the j-th function he wants to call (that depends on the type of the i-th variable), then you have to provide the mappings.

With this, the user will be able to call int v = c.fun<i, j>() without the knowledge of neither the type of the i-th variable, neither the name of the j-th function for this i-th variable.

template <typename M, int K> int fun(M const & m) const;

template <> int fun<M_1, 1>(M_1 const & m) const { return m.fun_1(); }
template <> int fun<M_1, 2>(M_1 const & m) const { return m.fun_2(); }
template <> int fun<M_1, 3>(M_1 const & m) const { return m.fun_3(); }

template <> int fun<M_2, 1>(M_2 const & m) const { return m.fun_1(); }
template <> int fun<M_2, 2>(M_2 const & m) const { return m.fun_2(); }
template <> int fun<M_2, 3>(M_2 const & m) const { return m.fun_3(); }

...

class C
{
    // Define the specialized class type for every N
    template <int N> class Mi { typedef void M; };
    template <> class Mi<1> { typedef M_1 M; };
    template <> class Mi<2> { typedef M_2 M; };
    template <> class Mi<3> { typedef M_3 M; };

    // Define the function to get the member N
    template <int N> Mi<N>::M const & get_M() const;
    template <> Mi<1>::M const & get_M() { return m1; } const;
    template <> Mi<2>::M const & get_M() { return m2; } const;
    template <> Mi<3>::M const & get_M() { return m3; } const;

    // Define the member function to call member N, function K
    template <int N, int K>
    int fun() { return fun<Mi<N>::M, K>( get_M<N>(); }

};

Now, if you want that the user can make calls with i and j as run-time variables, then this is not the way to go. Prefer an int fun(i, j) function with lots of if and switch. You cannot have both.

Didier Trosset
  • 36,376
  • 13
  • 83
  • 122
  • Thanks for writing out this direction. I had been thinking about it as well. But ultimately, this will not really reduce the amount of boilerplate that needs to be written. I think I'll stick with the `std::get` approach of alternative #3. – TemplateRex Feb 18 '13 at 10:29
  • I won't reduce the boilerplate, but you write it, instead of the caller. As I wrote, either you write the boilerplate to avoid the caller having to know the names of the fun_XY that changes from member to member, or the caller will have to. In your question the `C.get().fun_5()` may fail because `fun_5` is not available on `m1`. The caller has to care about it. In the end, this decision (you or the caller writes the boilerplate code) depends on whether there are numerous calls to the `fun_XY` functions, or not. – Didier Trosset Feb 18 '13 at 11:12
1

I would completely separate the update behaviour from the single element functionalities. All the M_i classes should implement an Updatable interface that simply contains the update method.

This allows you to safely expose N accessors to (non const) Updatable interfaces.

class Updatable{

public:
  virtual void update() = 0;
} ; 


class M_i : public Updatable{

public:
 void update();

};

Given the aggregate class C you can then:

  • expose N accessor to the const M_i classes

  • ask for the Updatable interface of a given M_i class. By accessing this (non-const) reference you can safely issue updates to any of the M_i instances.

  • call the delegate update directly.

.

class C{
    public:
              /** Returns the updatable interface related to M_1 */
              Updatable& getM_1Updater(){ return M_1}

              /** Returns the const reference to M_1*/
              const M_1& getM_1() const { return M_1}

              /** delegates update to each contained element */
              void update(){

                  m1.update();
                  m2.update();


                  [...]
              }

            };
Pierluigi
  • 2,212
  • 1
  • 25
  • 39
  • I like the `Updateable` interface, and in real code I would probably use that. However, I cannot expose non-const references to any of the member variables because the class `C` can only be updated for all its member simultaneously. It would break the class invariantst to do otherwise. – TemplateRex Feb 18 '13 at 10:04
  • In this case you might consider making the update method private in all M_i classes and simply make C friend of all M_i. This allows you to safely access the non const reference to all M_i and still be sure that no one else, except C, will ever call update. – Pierluigi Feb 18 '13 at 10:28
  • No, the `friend` approach is not something that I'll consider. I simply don't want to expose the updater interface of individual building blocks to clients of `C`. – TemplateRex Feb 18 '13 at 10:31
  • The friend relation can be limited to the container C and a variation of the Updateable interface (whose update() method should be now protected). – Pierluigi Feb 18 '13 at 11:14
1

Turning my comment into an answer.

If you decide to go with alternative 1 (N*K delegates), you can use Boost.Preprocessor to do the boilerplate work for you:

#include <boost/preprocessor.hpp>

// Define identifier names

#define FUNCTIONS (fun)(gun)(hun)

#define MEMBER_NAMES (m1_)(m2_)(m3_)

#define SUFFIXES (_1)(_2)(_3)


// Utility "data structure"
// Used to hand down state from iteration over functions to iteration over suffixes

#define WRAP_DATA(function, member) \
  (2, (function, member))

#define UNWRAP_DATA_FUNTION(data) \
  BOOST_PP_ARRAY_ELEM(0, data)

#define UNWRAP_DATA_MEMBER(data) \
  BOOST_PP_ARRAY_ELEM(1, data)


// Accessor-generating functionality

  // Convenience macro for generating the correct accessor name
#define CREATE_FUNCTION_NAME(data, suffix) \
  BOOST_PP_CAT(UNWRAP_DATA_FUNCTION(data), suffix)

  // Macro generating one accessor delegation
#define GENERATE_ACCESSOR(r, data, suffix) \
  int CREATE_FUNCTION_NAME(data, suffix) () const { return UNWRAP_DATA_MEMBER(data).CREATE_FUNCTION_NAME(data, suffix) (); }


// Generate accessors

class C
{

  // Execute GENERATE_ACCESSOR once for each element of SUFFIXES
#define BOOST_PP_LOCAL_MACRO(iter) \
  BOOST_PP_SEQ_FOR_EACH(GENERATE_ACCESSOR, WRAP_DATA(BOOST_PP_SEQ_ELEM(iter, FUNCTIONS), BOOST_PP_SEQ_ELEM(iter, MEMBER_NAMES)), SUFFIXES)

#define BOOST_PP_LOCAL_LIMITS (0, BOOST_PP_SEQ_SIZE(FUNCTIONS) - 1)

  // Execute BOOST_PP_LOCAL_MACRO once for each value within BOOST_PP_LOCAL_LIMITS
#include BOOST_PP_LOCAL_ITERATE()

// rest of class C here
// ...

};

Translated into pseudo-code to better highlight the working logic:

FUNCTIONS = {fun, gun, hun};
MEMBER_NAMES = {m1_, m2_, m3_};
SUFFIXES = {_1, _2, _3};

struct Data {
  auto function, member;
};

auto createFunctionName(data, suffix) {
  return data.function + suffix;
}

auto generateAccessor(data, suffix) {
  return "int " + createFunctionName(data, suffix) + "() const { return " + data.member + "." + createFunctionName(data, suffix) + "(); }";
}


class C
{

for (i = 0; i < sizeof(FUNCTIONS); ++i) {
  foreach (suffix in SUFFIXES) {
    generateAccessor(Data(FUNCTIONS[i], MEMBER_NAMES[i]), suffix);
  }
}

};
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • +1 nice to see that the old faithful preprocessor can help with this type of boilerplate. For aggregate classes `C` that also need to expose a read abstraction I would probably consider this. For my purposes, `C` only provides a single write abstraction, and is a behaviorless aggregate for read purposes. So I think alternative #3 serves my purposes most efficiently. – TemplateRex Feb 18 '13 at 10:34