Open iacopobaroncini opened 7 years ago
I can confirm this multiple expression evaluation problem has come up for people using Teensy.
The solution is not so simple, because the C++ std namespace also has min & max, and bringing in those headers causes all sorts of other compatibility headaches. These functions/macros are also needed for plain C, which is seldom used in the Arduino world, but failure to consider it will break some programs.
Here's a conversation from earlier this year about some of the details:
All crear Paul. Many thanks for your explanation. I actually had problems (with abs and round in first instance) while working with Arduino boards (micro and mega).
Maybe make both a templated function and a macro that calls the templated function? That would avoid the multiple call issue AND the namespace clash.
template<typename T> T &Max(T &a, T &b) { return (a>=b) ? a : b; }
template<typename T> const T &Max(const T &a, const T &b) { return (a>=b) ? a : b; }
#define max(a, b) Max((a), (b))
Some related discussion (about round()
specifically): arduino/ArduinoCore-API#76
(Note: The first half of this post is somewhat irrelevant, since what I suggest here was already implemented, so feel free to skip the first half)
Having a macro that calls a template function does not actually fix the namespace clash. Consider
#define max(a, b) Max((a), (b))
and later:
return std::max(1, 2);
The `std::max`` will still trigger the macro, breaking the build. I would say that there should not be macros for this at all (macros are fragile in general, but especially when they use names that are so likely to be used elsewhere too), just use expose the C++ templated function directly. This does need C++ templates to make sure the typing stays more or less the same as the current macros.
For C, that means there are two options:
I was halfway writing an example of how such a template function could look like, when I noticed that @facchinm actually already implemented exactly what I was suggesting above for the min
and max
functions:
Even more, he added a nice trick for the C macro version using "statement expressions" to store both parameters in local variables to prevent double evaluation.
So I think that this issue is already solved for the min
and max
functions, but the same fix should still be applied to the other macros (constrain
, radians
, degrees
, sq
, maybe also lowByte
, highByte
, bitRead
, bitSet
, bitClear
, bitWrite
, bit
and word
for good measure). Also note that https://github.com/arduino/ArduinoCore-avr/issues/324 mentions abs
and round
, which are apparentely defined by the AVR core, but not here? Is that intentional?
I implement some of these other functions in the template+macro style for another project here: https://github.com/ianfixes/arduino_ci/pull/144/commits/5fce911fb27e49e794b3d808ccbb1b5efdfc784e Those could serve as a basis for fixing them here too.
The math macros came up in the AVR core again (though for somewhat unrelated reasons) and prompted some discussion about changing the abs
macro.
First off, let me repeat a question from a previous comment: abs
and round
, are currently defined by the AVR core, but not here. On ArduinoCore-SAMD, it seems abs
is defined in the core-specific Arduino.h
instead of in the -API Common.h like the other macros. Also, it seems that -SAMD currently does not offer round
at all? Are these discrepancies intentional? Should abs
and round
be defined by -API for consistency? @facchinm, something you could comment on?
If they should, then I think they should also be converted to template functions, falling back to macros for C only, like the other functions mentioned in this issue.
Additionally, for abs, I would suggest changing the expression from ((x)>0?(x):-(x))
to `((x)>=0?(x)+0:-(x)) because:
>=
makes more sense than >
, no need to invert 0>=
produces slightly shorter code on AVR than >
(bonus benefit)See https://github.com/arduino/ArduinoCore-avr/pull/406 for additional discussion.
Hi folks,
I know it has been discussed already here and there...
I find semantically wrong the fact that the following math functions are implemented as macros: abs, min, max, constrain, round... and let me add bitWrite to the list.
It is well known that using these function the way the rest of the world does, introduces faults, and therefore generates failures.
Writing something like *abs(cos(x) - 123 sq(sin(x)) would introduce redundant calculations, just a crazy delay in this case... Writing something like abs(a += b) or abs(i++) just introduces a fault**... it does something different (wrong in fact).
Assuming that using template and inline functions would solve the issue with no extra executable space taken, I would like to know if there is any other good reason why those dangerous macro definitions are still in force.
Kind regards, Iacopo. #