I am developing a programming interface for several devices by various manufacturers. Most manufacturers typically produce at least a dozen models. Commands and data are sent to the devices as low-level instructions. The problem is that although no two devices support the same set of instructions and there is considerable overlap between the set of instructions that most devices support.
Because the low-level instructions are quirky I plan to wrap them in intuitively-named class methods, so that I don’t have to look up the docs when writing or reading (or debugging) code. In the first version of my design, all methods will belong to a Device class, whose constructor accepts a single parameter that is an enum indicating the model of a device. For example:
class Device
{
public:
enum Model{ ABC , KLM , XYZ };
Device( Model _model ); // ctor
// Commands (encapsulate low-level instructions)
inline void do_Foo(); // supported by all models
inline void do_Bar(); // unsupported by 'KLM'
};
However, in addition, I would like to prevent the command methods from being called if they are not supported by the model with which the Device was initialized. In fact, I would like to generate a compile time error if do_Bar() for example is called for the device model KLM. I’ve ruled out creating a class for each device model because this would involve creating scores of classes.
Thoughts
I’ve considered using the preprocessor directive #error in order to generate compile-time errors using the current device model as a predicate or precondition, although I’m not sure whether the preprocessor #if.. macros support non-constants, such as my device models. In an ideal world a command method would be tagged with the methods that support and are therefore allowed to call it. Yet, and I hope I’m not asking for too much, I would like this to be done as easily as possible, so that adding support for newer devices is relatively simple and doesn’t involve too many (error-prone) edits.
Afterthought: I realize by design may be flawed because all methods should be callable. I imagine that a subset of of valid commands can still be generated for each device using the STL, although I don’t know which STL paradigm (e.g. traits) applies in this case.
You can’t do compile time decisions on something which is only known at runtime (like a parameter passed to a compiler).
Therefore you basically have two options:
1) throw an exception at runtime, when an unsupported method is called
2) Create many classes, possibly through templates which contain only the appropriate methods. One way of doing that is the following:
The template parameter
Dummyis needed, sinceenable_ifdepends on SFINAE which will only work it if the method itself is a template method andenable_ifdepends on the template parameter. Since it is defaulted template parameter it doesn’t need to be explicitely mentioned when calling the method, sowill still work (so no change in the interface there).
I used
std::enable_if, which is only availible on C++11, if you don’t have that you need to either useboost::enable_if, or write it yourself (its not that hard).The second option has the disadvantage that it is not possible to write code, which doesn’t know the underlying model. On the plus side it allows you to mask slight differences in the offered interfaces through partial specialization (or using
enable_if) to get different implementations for different models.boost::enable_ifis different fromstd::enable_ifin that it takes a type as first parameter instead of a boolean. So one could either useboost::enable_if_c, which works just likestd::enable_ifor useboost::enable_ifin conjunction withboost::integral_constant(which is part of Boost Type Traits, so includeboost/type_traits.hpp):