I have a template class (function?) to act as a counter. I want for it to be able to use 8-, 16-, and 32-bit integers signed and unsigned. I have this working.
Since it’s a counter, I want to exclude any non-integer types. I’ve looked at typeid and the assert/static assert. A bit of searching on the forum (not hours and hours), and the compiler, tells me typeid is not supported in IDE 2.3.2.
What I've seen says use overloaded functions. Is this the preferred way or just a workaround?. Is there some secret to using assert/typeid?
--- .ino file ---
// 1. C++ primer on disk 6th ed. - class templates p. 830
// 2. utilizes GCC builtin function to check overflow
// https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins.html
// this version moves the class to a .h file, in a new IDE tab,
// in prepration for construction of a library.
#include "niceCounter.h" // specify local directory
niceCounter<uint16_t> ct1(5); // instantiation as integer with preset of five
niceCounter<uint8_t> ct2(100); // instantiation as byte integer with preset of one hundred
byte count1 = 4; // i/o assignment of pins
byte resetPin = 2;
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println(__FILE__);
Serial.println("starting");
delay(2000);
pinMode(count1, INPUT_PULLUP);
pinMode(resetPin, INPUT_PULLUP);
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
static unsigned long lastMillis;
if (millis() != lastMillis) { // generate a clock for the counter(s)
ct2.countUp(true);
lastMillis = millis(); // reset the clock
} else {
ct2.countUp(false);
}
if (ct2.isDone()) {
ct1.countUp(true);
ct2.reset(true);
} else {
ct1.countUp(false);
ct2.reset(false);
}
if (ct1.isOverFlowed()) {
digitalWrite(LED_BUILTIN, HIGH);
} else digitalWrite(LED_BUILTIN, LOW);
Serial.print(ct1.getAccValue());
Serial.print("\t\t");
Serial.println(ct2.getAccValue());
ct1.reset(digitalRead(resetPin) ? false : true);
}
=== .h file ===
#ifndef NICECOUNTER_H
#define NICECOUNTER_H
/*
Library form of templated counter. For signed and
unsigned integers 8-, 16-, 32-bit.
This code handles the false-true logic for user.
Preset value (to signify completion) is given at
instantiation.
Uses GCC builtin functions to detect overflow/underflow.
https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins.html
Provides these functions:
countUp,
countDown,
isDone,
isOverflowed,
isUnderflowed,
getAccValue,
reset
*/
template<typename T = int16_t> // assign default type
class niceCounter {
T accValue;
T presetValue;
T temp; // ovf test result
bool resestIn;
bool osrUp, osrUpSetup, overflow;
bool osrDown, osrDownSetup, underflow;
public:
niceCounter(T pre) { // constructor
presetValue = pre;
};
~niceCounter(){}; // destructor
/*
niceCounter member functions
*/
void countUp(bool increment) {
// Adds one to accValue when increment goes false-to-true
// State change detection handled internally
//
osrUp = increment and osrUpSetup;
osrUpSetup = !increment;
// osrUp true for one scan when increment goes false-to-true
if (osrUp) {
if (__builtin_add_overflow(accValue, 1, &temp)) {
overflow = true;
}
accValue++;
}
}
void countDown(bool decrement) {
// Subtracts one from accValue when increment goes false-to-true
// State change detection handled internally
//
osrDown = decrement and osrDownSetup;
osrDownSetup = !decrement;
// osrDown true for one scan when decrement goes false-to-true
if (osrDown) {
if (__builtin_sub_overflow(accValue, 1, &temp)) {
underflow = true;
}
accValue--;
}
}
bool isOverFlowed() {
return overflow;
}
bool isUnderFlowed() {
return underflow;
}
void reset(bool resetIn) {
if (resetIn) {
accValue = 0;
overflow = false;
underflow = false;
}
}
T getAccValue() {
return accValue;
}
bool isDone() {
return accValue >= presetValue;
}
}; // niceCounter terminates
#endif
typeid requires RunTime Type Information, and boards disable it with the -fno-rtti flag when compiling, since that adds overhead.
What kind of non-integer are you hoping to prevent? Floating-point? Does the code compile? Does it work? From a quick glance, It would have to be a type that supports the -- operator and can be assigned zero, or it will fail to compile anyway.
Not sure what you saw about overloaded functions, but here's one way to use them with static_assert
It complains for three of those declarations at compile-time, with
In instantiation of 'class Foo<float>':
required from here
error: static assertion failed
static_assert(allowedFoo(x));
^~~~~~~~~~~~~
All the constexpr means that things can be evaluated at compile-time, including the static_assert of the class-static value. ("static" is an overloaded term, C++-pun intended.) It appears to take neither program nor dynamic memory -- commenting it out does not alter the bytes used (trying this on R4).
Some pretty exotic C++ stuff there! +1 to both of you. Can't you also do something with #define to limit the datatypes that can be fed to the template?
Whether or not a C++ feature is supported is not a function of the Arduino IDE version, but of the target platform and its version.
std::is_integral is available on some platforms. It provides a compile-time confirmation that its argument is an integral type. This code behaves as expected for ESP32:
In the same idea as @kenb4 , if you want this to work on any platform, you might have to do the type checking yourself (which is easy in this case as you know the allowed types)
You could define yourself a traits class which is just a type that carries compile-time information. Using a struct (or class ) allows you to define static members, specialize the template for specific types, and use it in static_assert or other compile-time contexts.
I've also been wondering whether a template can restrict the types it accepts, perhaps even with forcing an implicit cast to one of the accepted types. I guess, essentially not
You'd thing that list of integral types would be sufficient. But, depending on the platform, it seems to fail for types like int, unsigned int, or char.
I couldn't see how to adapt @kenb4's example so I marked the example posted by J-M-L as solution because I could get it to work (and I do have a Trinket lying here on which I might someday want to have a counter).
The examples and guidance are much appreciated.
Running on an UNO
.ino:
// template code from that offered by J-M-L at
// https://forum.arduino.cc/t/exclude-non-integers-in-template/1409112
// 1. C++ primer on disk 6th ed. - class templates p. 830
// 2. utilizes GCC builtin function to check over/underflow
// https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins.html
// this version moves all template/class code to a header file
// in a new IDE tab, and is called as a library.
#include "niceCounter.h" // specify local directory
//---------------------------
niceCounter<int8_t> counter(10);
niceCounter<> counter2(10); // type defaults to int
niceCounter<uint32_t> badCounter(3);
void setup() {
Serial.begin(115200);
// verify widths of instantiated counters
Serial.println("niceCounter demo on Uno");
Serial.print("size counter2 - ");
Serial.println(counter2.counterSize);
Serial.print("size counter - ");
Serial.println(counter.counterSize);
Serial.print("size badcounter - ");
Serial.println(badCounter.counterSize);
delay(4500);
}
void loop() {
static unsigned long lastMillis = 0;
if (millis() - lastMillis > 500) { // increment every 0.5s
counter.countUp(true);
lastMillis = millis();
} else {
counter.countUp(false);
}
Serial.print("Count: ");
Serial.print(counter.getAccValue());
Serial.print("\tDone: ");
Serial.print(counter.isDone() ? "Yes" : "No");
Serial.print("\t");
counter.isDone() ? counter2.countUp(true) : counter2.countUp(false);
Serial.println(counter2.getAccValue());
if (counter.isDone()) {
counter.reset(true);
}
delay(100);
}
.h:
#ifndef NICECOUNTER_H
#define NICECOUNTER_H
/*
Library form of templated counter. For signed and
unsigned integers 8-, 16-, 32-bit.
This code handles the false-true logic for user.
Preset value (to signify completion) is given at
instantiation.
Uses GCC builtin functions to detect overflow/underflow.
https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins.html
Provides these functions:
countUp,
countDown,
isDone,
isOverflowed,
isUnderflowed,
getAccValue,
reset
*/
// clang-format off
template<typename T> struct is_integral_type
{ static constexpr bool value = false; };
template<> struct is_integral_type<uint8_t>
{ static constexpr bool value = true; };
template<> struct is_integral_type<int8_t>
{ static constexpr bool value = true; };
template<> struct is_integral_type<uint16_t>
{ static constexpr bool value = true; };
template<> struct is_integral_type<int16_t>
{ static constexpr bool value = true; };
template<> struct is_integral_type<uint32_t>
{ static constexpr bool value = true; };
template<> struct is_integral_type<int32_t>
{ static constexpr bool value = true; };
template<typename T = int16_t> // assign default type
class niceCounter {
T accValue=0;
T presetValue;
T temp=0; // ovf/uf test result
bool resestIn;
bool osrUp, osrUpSetup, overflow;
bool osrDown, osrDownSetup, underflow;
public:
T counterSize=sizeof(presetValue);
niceCounter(T pre) { // constructor
presetValue = pre;
};
~niceCounter(){}; // destructor
/*
niceCounter member functions
*/
void countUp(bool increment) {
// Adds one to accValue when increment goes false-to-true
// State change detection handled internally
//
osrUp = increment and osrUpSetup;
osrUpSetup = !increment;
// osrUp true for one scan when increment goes false-to-true
if (osrUp) {
if (__builtin_add_overflow(accValue, 1, &temp)) {
overflow = true;
}
accValue++;
}
}
void countDown(bool decrement) {
// Subtracts one from accValue when decrement goes false-to-true
// State change detection handled internally
//
osrDown = decrement and osrDownSetup;
osrDownSetup = !decrement;
// osrDown true for one scan when decrement goes false-to-true
if (osrDown) {
if (__builtin_sub_overflow(accValue, 1, &temp)) {
underflow = true;
}
accValue--;
}
}
bool isOverFlowed() {
return overflow;
}
bool isUnderFlowed() {
return underflow;
}
void reset(bool resetIn) {
if (resetIn) {
accValue = 0;
overflow = false;
underflow = false;
}
}
T getAccValue() {
return accValue;
}
bool isDone() {
return accValue >= presetValue;
}
}; // niceCounter terminates
#endif
Hmm. I tested it by substituting float for one of the types and it gives an error -
niceCounter<float> counter(10);
In file included from C:\Users\User\OneDrive\Documents\Arduino\template_counter_v1_6_with_jml_type_checking\template_counter_v1_6_with_jml_type_checking.ino:12:0:
C:\Users\User\OneDrive\Documents\Arduino\template_counter_v1_6_with_jml_type_checking\niceCounter.h: In instantiation of 'void niceCounter<T>::countUp(bool) [with T = float]':
C:\Users\User\OneDrive\Documents\Arduino\template_counter_v1_6_with_jml_type_checking\template_counter_v1_6_with_jml_type_checking.ino:37:25: required from here
C:\Users\User\OneDrive\Documents\Arduino\template_counter_v1_6_with_jml_type_checking\niceCounter.h:80:34: error: argument 1 in call to function '__builtin_add_overflow' does not have integral type
}
^
exit status 1
Compilation error: argument 1 in call to function '__builtin_add_overflow' does not have integral type
static_assert(is_integral_type<T>::value, "niceCounter requires an integral type");
got left out of the version I worked with - and posted. It can't have helped either to have set this aside for awhile.
I wondered about this because I also substituted type char and did not get an error with my posted code. I assumed char was being interpreted as an integer type and all was good. Adding the static_assert now returns the proper compilation error message with type char.
Presumably, that's because the char type is supported by __builtin_add_overflow (unlike float type). It failed when you added the static_assert because char is not one of the enumerated types in the template specializations for is_integral_type. So, the default case returns false. char is in fact a valid integer type (typically single-byte), so you could add a specialization for it if you wished. However that may be more trouble than it's worth as whether char is treated as signed or unsigned is implementation-dependent.