runEvery (the next Blink Without Delay)

Based off of pYro_65's post, I have come up with this:

#ifdef __cplusplus
template <bool _Flag, typename _True, typename _False> struct If{ typedef _True Result; };
template <typename _True, typename _False> struct If<false, _True, _False>{ typedef _False Result; };

template< uint64_t _Number >
class BestFitUInt{
protected:
    static const uint32_t max_32 =  ((~(uint32_t)0) - 100);
    static const uint16_t max_16 =  ((~(uint16_t)0) - 100);
    static const uint8_t  max_8  =  ((~(uint8_t )0) - 100);

    // uint64_t should be an error.. Perhaps use this to link in a file with
    //  #error explaining better?
    typedef typename If< (_Number <= max_32), uint32_t, uint64_t >::Result tA;
    typedef typename If< (_Number <= max_16), uint16_t, tA >::Result tB;
public:
    typedef typename If< (_Number <= max_8 ), uint8_t , tB >::Result Result;
};

#define RETYPE(n) BestFitUInt< n >::Result

#else // cplusplus
#define RETYPE(n) uint32_t
#endif //cplusplus

#define runEvery(t) for (static  RETYPE(t) _lasttime;\
                         (RETYPE(t))((RETYPE(t))millis() - _lasttime) >= (t);\
                         _lasttime += (t))

tested in this sketch:

#ifdef __cplusplus
template <bool _Flag, typename _True, typename _False> struct If{ typedef _True Result; };
template <typename _True, typename _False> struct If<false, _True, _False>{ typedef _False Result; };

template< uint64_t _Number >
class BestFitUInt{
protected:
public: // Remove for final version
    static const uint32_t max_32 =  ((~(uint32_t)0) - 100);
    static const uint16_t max_16 =  ((~(uint16_t)0) - 100);
    static const uint8_t  max_8  =  ((~(uint8_t )0) - 100);

    // uint64_t should be an error.. Perhaps use this to link in a file with
    //  #error explaining better?
    typedef typename If< (_Number <= max_32), uint32_t, uint64_t >::Result tA;
    typedef typename If< (_Number <= max_16), uint16_t, tA >::Result tB;
public:
    typedef typename If< (_Number <= max_8 ), uint8_t , tB >::Result Result;
};

#define RETYPE(n) BestFitUInt< n >::Result

#else // cplusplus
#define RETYPE(n) uint32_t
#endif //cplusplus

#define runEvery(t) for (static  RETYPE(t) _lasttime;\
                         (RETYPE(t))((RETYPE(t))millis() - _lasttime) >= (t);\
                         _lasttime += (t))

#define DEBUG(x) do {Serial.print(#x ": "); Serial.print(x); Serial.println(/*'\t'*/); } while (0)

#define LOOKATTYPE(n) do {typename BestFitUInt< n >::Result var; Serial.print("Number: "); Serial.print(n); Serial.print("\tType: "); Serial.println(mytypeof(var)); } while(0)

template <class T> const char* mytypeof(T&) { return "unknown";}
template <> const char* mytypeof(uint8_t &) { return "uint8_t ";}
template <> const char* mytypeof(uint16_t&) { return "uint16_t";}
template <> const char* mytypeof(uint32_t&) { return "uint32_t";}
template <> const char* mytypeof(uint64_t&) { return "uint64_t";}

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

    DEBUG(BestFitUInt<5>::max_32);
    DEBUG(BestFitUInt<5>::max_16);
    DEBUG(BestFitUInt<5>::max_8);
    
    Serial.println();

    LOOKATTYPE(126);
    LOOKATTYPE(127);
    LOOKATTYPE(128);

    Serial.println();

    LOOKATTYPE(154);
    LOOKATTYPE(155);
    LOOKATTYPE(156);

    Serial.println();

    LOOKATTYPE(65434);
    LOOKATTYPE(65435);
    LOOKATTYPE(65436);

    Serial.println();

    LOOKATTYPE(65534);
    LOOKATTYPE(65535);
    LOOKATTYPE(65536);

    Serial.println();

    LOOKATTYPE(4294967194);
    LOOKATTYPE(4294967195);
    LOOKATTYPE(4294967196);

    Serial.println();

    LOOKATTYPE(4294967294);
    LOOKATTYPE(4294967295);
    //LOOKATTYPE(4294967296); // Serial.print can't handle 64 bit

}

void loop() {

}

Which prints out this:

BestFitUInt<5>::max_32: 4294967195
BestFitUInt<5>::max_16: 65435
BestFitUInt<5>::max_8: 155

Number: 126	Type: uint8_t 
Number: 127	Type: uint8_t 
Number: 128	Type: uint8_t 

Number: 154	Type: uint8_t 
Number: 155	Type: uint8_t 
Number: 156	Type: uint16_t

Number: 65434	Type: uint16_t
Number: 65435	Type: uint16_t
Number: 65436	Type: uint32_t

Number: 65534	Type: uint32_t
Number: 65535	Type: uint32_t
Number: 65536	Type: uint32_t

Number: 4294967194	Type: uint32_t
Number: 4294967195	Type: uint32_t
Number: 4294967196	Type: uint64_t

Number: 4294967294	Type: uint64_t
Number: 4294967295	Type: uint64_t

It checks the number and if it's more than (100 less than the maximum value of the type) then it promotes it to the next type. Thus, even if the user made used the value 65435 (100 less than maximum), they would still have to have 100 milliseconds of calculation in their loop for it to fail. That value could be made greater at little cost (as most values would probably be about 500, which is much much less than the maximum value for an int).

If it's not using C++, it just makes it 32 bit.

My next goal is to make the macro work even if t is not a constant, using the gcc builtin _bulitin_constant_p. Unfortunately, that function/macro/thing doesn't appear to work properly, as this fails to compile:

template<int t> struct test { static const int num = t; };

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

    volatile int a = 5;

    int b = test<_builtin_constant_p(a)>::num;

    Serial.print("b: ");
    Serial.print(b);
}

void loop() {
}
build-cli/tmp.cpp: In function 'void setup()':
build-cli/tmp.cpp:9:38: error: 'a' cannot appear in a constant-expression
build-cli/tmp.cpp:9:39: error: a function call cannot appear in a constant-expression
build-cli/tmp.cpp:9:40: error: template argument 1 is invalid

... which is silly because even though a is not constant, _builtin_constant_p(a) should be constant. So, workarounds are necessary.

My other goal, as seen in the comments, is to get a good-looking error if someone tries to run the macro with something like 4294967294 (less than 100 below maximum 32 bit integer). I thought that maybe it could use a class that then linked in another cpp file that had nothing but that class and a #error "value for runEvery is too high"

Aparently contrary to popular belief, I welcome comments :slight_smile: