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