2

How does one provide a unified interface to sets of functions, that are used in the same way? To illustrate, please look at the set of given library functions:

/* existing library functions */
/* the signatures are different: some return int, some float */

/* set of input related functions */
int getInputValue() { return 42; }
size_t getInputSize() { return 1; }

/* set of output related functions */
int getOutputValue() { return 21; }
size_t getOutputSize() { return 1; }

/* set of parameter related functions */
float getParameterValue() { return 3.14; }
size_t getParameterSize() { return 1; }

and assume they are used in the same way:

if (getSize() > 0) {
  T value = getValue()

A) What is a good way to provide getSize() and getValue()?

I first though that Template Method Pattern is what I want, but I couldn't apply it, because in contrast to the Worker in the Template Method Pattern, my functions have different signatures.

So what I did instead:

/* I want to provide a uniform interface */

/* the specific part of inputs, outputs and parameters is in the traits */
struct input_traits {
  typedef int value_type;
  static int getValue() { return getInputValue(); }
  static size_t getSize() { return getInputSize(); }
};

struct output_traits {
  typedef int value_type;
  static int getValue() { return getOutputValue(); }
  static size_t getSize() { return getOutputSize(); }
};

struct parameter_traits {
  typedef float value_type;
  static float getValue() { return getParameterValue(); }
  static size_t getSize() { return getParameterSize(); }
};


/* the common part (they are used in the same way) is in the Helper */
template<typename traits>
class CommonUsage {
public:
  void use()
  {
    if (traits::getSize() > 0) {
      typename traits::value_type value = traits::getValue();
    }
  }
};

int main()
{
  CommonUsage<input_traits>().use();
  CommonUsage<output_traits>().use();
  CommonUsage<parameter_traits>().use();
}

B) Is this a good approach?

Dave Schweisguth
  • 36,475
  • 10
  • 98
  • 121
Micha Wiedenmann
  • 19,979
  • 21
  • 92
  • 137
  • It might help to give information on the underlying data structures these functions are describing. For example, are these tuples, arrays, functions? In some cases you might be able to use template specialization to deal with the different cases. – Abe Schneider Oct 12 '12 at 15:39

2 Answers2

2

A. If i understood your question correctly you should use an abstract class. Look at the next code, it essentially does the same as your code.

This is how i would do it.

#include <iostream>

template <typename value_type>
class Traits {
public:
    virtual value_type getValue() const = 0;
    virtual size_t getSize() const = 0;

    virtual ~Traits() { }
};

class input_traits: public Traits <int>{
public:
    virtual int getValue() const {
        return 42;
    }

    virtual size_t getSize() const {
        return 1;
    }
};

class parameter_traits: public Traits <double>{
public:
    virtual double getValue() const {
        return 3.14;
    }
    virtual size_t getSize() const {
        return 1;
    }
};

class CommonUsage {
public:
    template <typename value_type>
    void use(const Traits<value_type>& traitsObject) {
        if (traitsObject.getSize() > 0) {
            std::cout << traitsObject.getValue();
        }
    }
};

int main() {
    CommonUsage().use(parameter_traits());
    return 0;
}
user1708860
  • 1,683
  • 13
  • 32
0

As an alternative to user's answer (this time using template specialization) is:

template <class T>
struct traits {
    T getValue() const {  throw std::runtime_exception("..."); }
    size_t getSize() const { return 0; }        
}; 


template <>
struct traits<int> {
    int getValue() const { return 42; }
    size_t getSize() const { return 1; }
};

template <>
struct traits<float> {
    int getValue() const { return 3.145; }
    size_t getSize() const { return 1; }
};

// do template aliasing
using input_traits = traits<int>;
using parameter_traits = traits<float>;

struct CommonUsage {
    template <typename T>
    static void use(const traits<T> &traits) {
        if (traits.getSize() > 0)
            std::cout << traits.getValue() << std::endl;
    }
};

int main(int arg, char **argv) {
    CommonUsage::use(input_traits());
    CommonUsage::use(parameter_traits());
}

There are advantages/disadvantages to both approaches. If you use template specialization, you don't pay the overhead of virtual methods.

Abe Schneider
  • 977
  • 1
  • 11
  • 23
  • I should add, CommonUsage isn't really required -- I added it just to match styles. The 'use' function could easily live on it's own. In fact it's tempting to do away with the classes altogether. However, template specialization requires classes to work. Thus, it's no uncommon to provide a function like 'use' that hides the details and can automatically infer the type for you. – Abe Schneider Oct 12 '12 at 16:49
  • @ AbeSchneider: One tricky and important part of the question is that inputs and outputs have the same return type (`int`) but different traits, so they have to be different, which is impossible with `using input_traits = traits;` and `using output_traits = traits;`. (Infact I started with a version similar to yours in the first place.) – Micha Wiedenmann Oct 12 '12 at 22:22