Select behavior (methods) at compile time

With the assistance of the forum I have developed a timer class and a library to hold it. There are currently three timer variants available through one instantiation. I'd add more choices but the logic to run the timer and control the output based on timer type gets ugly quickly using only one basic skeleton – lots of ifs and logical compares. An example of what's possible.

What I think should be possible is to create a number of timer types all under the umbrella of one class, each with its own behavior, and select one of those options at compile time. For instance, every timer type will have an input called ‘enable’ which initiates and/or controls the particular timer’s behavior, and an output 'done' which tells the world the timer has responded to the input, which response will vary depending on type.

Any of the available types would be selected by: PLCtimer( type, preset);

An example: standardTimer will accumulate time when enable is true and done returns true when preset is reached. It resets to zero accumulated value and done returns false when enable is false.

latchedTimer – will start accumulating time when enable goes true but thereafter is independent of enable, it continues timing ‘til reaching preset whereupon done returns true and remains true until a false-to-true transition is seen at enable. This event resets the timer.

The questions:

What is doing something like this (choosing one option from several at compile time) called?

Are there multiple ways to skin this cat?

May I assume the compiler will optimize away the unused blocks of code and only install that which is selected?

Thanks for looking.

You are versed in the pre-compiler directives?

DKWatson:
You are versed in the pre-compiler directives?

Ummmm. That would be a "No".

Is that something distinct from 'pre-processor' directives?

I've attached the gcc pre-compiler manual. Have a look through that and we can discuss it further. Essential there are a host of conditional compilation directives designed to do exactly what you want. What must be established prior to of course, is the condition against which you will test. As a quick example,

#ifdef __AVR_ATmegamega2560__
    #define     LED_PORT    PORTB
    #define     LED_PIN     PB7
    const byte BOARD[]      = {"mega2560"};
#endif

#ifdef __AVR_ATmega328P__
    #define     LED_PORT    PORTB
    #define     LED_PIN     PB5
    const byte BOARD[]      = {"328P"};
#endif

will set the LED port based on the board. The macros AVR_ATmega328P and AVR_ATmegamega2560 are defined in the board specific header.

The C Preprocessor 8.1.pdf (455 KB)

Pre-compiler, pre-processor, I guess it depends on your generation. I'm too old.

DKWatson:
I've attached the gcc pre-compiler manual. Have a look through that and we can discuss it further.

Thanks! Reading now. This is more accessible than a lot of other language/syntax descriptions.

Normally, you'd have three classes that implement virtual methods.

To do what you describe, you could use a 'strategy' pattern. The timer class would be a wrapper around an implementation which would be created in the constructor.

class ColorPrinter {
    class Impl {
      public:
        virtual void printMe() = 0;
    };

    class RedImpl : Impl {
        void printMe() {
          Serial.print("RED");
        }
    };

    class GreenImpl : Impl {
        void printMe() {
          Serial.print("GREEN");
        }
    };

    const Impl *impl;

  public:

    ColorPrinter(boolean useRed) : impl( useRed ? (Impl*) new RedImpl() : (Impl*) new GreenImpl()) {
    }

    void print() {
      impl->printMe();
    }

};

ColorPrinter redPrinter(true);
ColorPrinter greenPrinter(false);

void setup() {
  Serial.begin(9600);

  Serial.print("redPrinter.print() = ");
  redPrinter.print();
  Serial.println();

  Serial.print("greenPrinter.print() = ");
  greenPrinter.print();
  Serial.println();
}

void loop() {
}

PaulMurrayCbr:
Normally, you'd have three classes that implement virtual methods.

To do what you describe, you could use a 'strategy' pattern. The timer class would be a wrapper around an implementation which would be created in the constructor.

Might be a non-starter since 'virtual methods' are unknown to me. Looking it up now.

Rather than having one class that behaves in different ways, perhaps you should have a base class and derived classes, each of which behaves in one way. That would be easier to follow.

class PLCtimer;
class PLCLatchedTimer : PLCTimer;
class PLCStandrdTimer : PLCTimer;

Read about "inheritance": http://www.cplusplus.com/doc/tutorial/inheritance/

Thanks for all the replies. I do appreciate it. The suggestions so far:

DKWatson:
You are versed in the pre-compiler directives?

PaulMurrayCbr:
Normally, you'd have three classes that implement virtual methods.

johnwasser:
Rather than having one class that behaves in different ways, perhaps you should have a base class and derived classes, each of which behaves in one way. That would be easier to follow.

At this point preprocessor directives seem the most accessible to me because I have some understanding of if/else and ifdef/ifndef. I'm guessing these two would play a key role in what I hope to do.

I've been reading but other two approaches - @PaulMurrayCbr's virtual methods and @johnwasser's derived classes - are stretching me.

From the standpoint of keeping the stated [color=blue]PLCtimer(type, preset);[/color] as the creator of the various types, how do each of the offered solutions accomplish this? Reading the tutorials/docs I can't see how [color=blue]type[/color] influences which sort of timer is generated.

dougp:
From the standpoint of keeping the stated [color=blue]PLCtimer(type, preset);[/color] as the creator of the various types, how do each of the offered solutions accomplish this? Reading the tutorials/docs I can't see how [color=blue]type[/color] influences which sort of timer is generated.

You can't change the declaration of the object at run time. All you can do with a "type" argument to the constructor is to store the 'type' and have the methods behave differently depending on the type. In your cases of StandardTimer and LatchedTimer types the differences appear to be in the handling of "enable" changes. Just use 'if' or 'switch' statements to tailor behavior of each method to the current timer 'type'.

johnwasser:
You can't change the declaration of the object at run time.

Right, I don't want to be able to change it on the fly. What I'm after is some mechanism by which the compiler recognizes the [color=blue]type[/color]and keeps only code to create that type while discarding/optimizing out irrelevant code.

DKWatson:
I've attached the gcc pre-compiler manual. Have a look through that and we can discuss it further.

I've looked over the conditionals in the manual - and saved a copy of the .pdf. I'm ready to see how this might be done.

Let us know how you make out.

DKWatson:
Let us know how you make out.

I was referring to your post #3. If I could understand how to make the preprocessor aware of thetypevariable I feel I could make some progress. Presently I don't see any way to make that happen.

There are some things the compiler can't figure out on it's own. If you want conditional compilation based on pre-existing parameters, simply define them before your #if directives.

As an example, I conditionally compile a fifo buffer struct based on how much memory I choose to dedicate to the storage array. Near the top of the code I have #define BUFLGTH 32 (or 64 or 128 or whatever).

Further down there are a series of conditional declarations based on BUFLGTH:

#define BUFLGTH 32

typedef struct
{
   #if BUFLGTH == 32
      byte data[32];
      uint16_t first:  5;
      uint16_t next:   5;
      uint16_t unused: 6;
#endif
#if BUFLGTH == 64
   .

blah, blah, blah.

This allows me to setup the array and the bitfields for index rollover based on whatever length of buffer I choose, but must be specified at compile.

Based on my understanding of the replies so far, I fear I have insufficiently communicated what I'd like to create. With varying levels of clarity I can see how the approaches given will produce an instance of this or that timer type, with @DKWatson's #define technique being the most straightforward.

The goal is to be able to produce, for example, an instance of standardTimer *and * latchedTimer *and * variant3Timer in the same sketch from the basic form of:

[color=blue]PLCtimer timerName(preset, timer type);[/color]

with the resulting instantiation of each comprising only the code to implement that particular type - my thinking is this will speed execution and reduce compiled code size.

As in, if type == TON a PLCTimer object is created that only includes TON code. Same goes if type == LATCHED. Is this possible?

dougp:
The goal is to be able to produce, for example, an instance of standardTimer *and *latchedTimer *and *variant3Timer in the same sketch from the basic form of:

[color=blue]PLCtimer timerName(preset, timer type);[/color]

with the resulting instantiation of each comprising only the code to implement that particular type - my thinking is this will speed execution and reduce compiled code size.

Is this possible?

I don't think it is possible. The PLCtimer class is implemented in a separate file. When that file is compiled there is no information about what values of 'timerType' will be used in your other compilation units so the compiler compiles all of the functions. The linker is smart enough to not include functions that are not called.

johnwasser:
I don't think it is possible. The PLCtimer class is implemented in a separate file. When that file is compiled there is no information about what values of 'timerType' will be used in your other compilation units so the compiler compiles all of the functions. The linker is smart enough to not include functions that are not called.

Thank you. I was basing my hope on the compiler recognizing attributes (earlier thread, post #7) and taking some action.

I borrowed C++ for Dummies and have been looking at the sections on the previously mentioned derived classes/virtual functions/inheritance :confused: since it now appears this area is the way forward.

Soon I'll be able to ask better silly questions!