Reputation: 21514
We have an algorithm library doing lots of std::min
/std::max
operations on numbers that could be NaN. Considering this post: Why does Release/Debug have a different result for std::min?, we realised it's clearly unsafe.
Is there a way to prevent developers from using std::min
/std::max
?
Our code is compiled both with VS2015 and g++. We have a common header file included by all our source files (through /FI
option for VS2015 and -include
for g++). Is there any piece of code/pragma that could be put here to make any cpp file using std::min
or std::max
fail to compile?
By the way, legacy code like STL headers using this function should not be impacted. Only the code we write should be impacted.
Upvotes: 12
Views: 6012
Reputation: 334
IMO the failure of the C++ language standard to require min(NaN, x) and min(x, NaN) to return NaN and similarly for max is a serious flaw in the C++ language standards, because it hides the fact that a NaN has been generated and results in surprising behaviour. Very few software developers do sufficient static analysis to ensure that NaNs can never be generated for all possible input values. So we declare our own templates for min and max, with specialisations for float and double to give correct behaviour with NaN arguments. This works for us, but might not work for those who use larger parts of the STL than we do. Our field is high integrity software, so we don't use much of the STL because dynamic memory allocation is usually banned after the startup phase.
Upvotes: -1
Reputation: 3701
I'm going a bit philosophical here and less code. But I think the best approach would be to educate those developers, and explain why they shouldn't code in a specific way. If you'll be able to give them a good explanation, then not only will they stop using functions that you don't want them to. They will be able to spread the message to other developers in the team.
I believe that forcing them will just make them come up with work arounds.
Upvotes: 18
Reputation: 4020
If you compile using GCC or Clang, you can poison these identifiers.
#pragma GCC poison min max atoi /* etc ... */
Using them will issue a compiler error:
error: attempt to use poisoned "min"
The only problem with this in C++ is you can only poison "identifier tokens", not std::min
and std::max
, so actually also poisons all functions and local variables by the names min
and max
... maybe not quite what you want, but maybe not a problem if you choose Good Descriptive Variable Names™.
If a poisoned identifier appears as part of the expansion of a macro which was defined before the identifier was poisoned, it will not cause an error. This lets you poison an identifier without worrying about system headers defining macros that use it.
For example,
#define strrchr rindex #pragma GCC poison rindex strrchr(some_string, 'h');
will not produce an error.
Read the link for more info, of course.
https://gcc.gnu.org/onlinedocs/gcc-3.3/cpp/Pragmas.html
Upvotes: 3
Reputation: 52548
If you get different results in debug and release, then the problem isn't getting different results. The problem is that one version, or probably both, are wrong. And that isn't fixed by disallowing std::min or std::max or replacing them with different functions that have defined results. You have to figure out which outcome you would actually want for each function call to get the correct result.
Upvotes: 4
Reputation: 22562
I'm not going to answer your question exactly, but instead of disallowing std::min
and std::max
altogether, you could educate your coworkers and make sure that you are consistently using a total order comparator instead of a raw operator<
(implicitly used by many standard library algorithms) whenever you use a function that relies on a given order.
Such a comparator is proposed for standardization in P0100 — Comparison in C++ (as well as partial and weak order comparators), probably targeting C++20. Meanwhile, the C standard committee has been working for quite a while on TS 18661 — Floating-point extensions for C, part 1: Binary floating-point arithmic, apparently targeting the future C2x (should be ~C23), which updates the <math.h>
header with many new functions required to implement the recent ISO/IEC/IEEE 60559:2011 standard. Among the new functions, there is totalorder
(section 14.8), which compares floating point numbers according to the IEEE totalOrder
:
totalOrder(x, y) imposes a total ordering on canonical members of the format of x and y:
- If x < y, totalOrder(x, y) is true.
- If x > y, totalOrder(x, y) is false.
- If x = y
- totalOrder(-0, +0) is true.
- totalOrder(+0, -0) is false.
- If x and y represent the same floating point datum:
- If x and y have negative sign, totalOrder(x, y) is true if and only if the exponent of x ≥ the exponent of y.
- Otherwise totalOrder(x, y) is true if and only if the exponent of x ≤ the exponent of y.
- If x and y are unordered numerically because x or y is NaN:
- totalOrder(−NaN, y) is true where −NaN represents a NaN with negative sign bit and y is a floating-point number.
- totalOrder(x, +NaN) is true where +NaN represents a NaN with positive sign bit and x is a floating-point number.
- If x and y are both NaNs, then totalOrder reflects a total ordering based on:
- negative sign orders below positive sign
- signaling orders below quiet for +NaN, reverse for −NaN
- lesser payload, when regarded as an integer, orders below greater payload for +NaN, reverse for −NaN.
That's quite a wall of text, so here is a list that helps to see what's greater than what (from greater to lesser):
Unfortunately, this total order currently lacks library support, but it is probably possible to hack together a custom total order comparator for floating point numbers and use it whenever you know there will be floating point numbers to compare. Once you get your hands on such a total order comparator, you can safely use it everywhere it is needed instead of simply disallowing std::min
and std::max
.
Upvotes: 3
Reputation: 217478
As modifying std
is disallowed, following is UB, but may work in your case.
Marking the function as deprecated:
Since c++14, the deprecated attribute:
namespace std
{
template <typename T>
[[deprecated("To avoid to use Nan")]] constexpr const T& (min(const T&, const T&));
template <typename T>
[[deprecated("To avoid to use Nan")]] constexpr const T& (max(const T&, const T&));
}
And before
#ifdef __GNUC__
# define DEPRECATED(func) func __attribute__ ((deprecated))
#elif defined(_MSC_VER)
# define DEPRECATED(func) __declspec(deprecated) func
#else
# pragma message("WARNING: You need to implement DEPRECATED for this compiler")
# define DEPRECATED(func) func
#endif
namespace std
{
template <typename T> constexpr const T& DEPRECATED(min(const T&, const T&));
template <typename T> constexpr const T& DEPRECATED(max(const T&, const T&));
}
Upvotes: 7
Reputation: 6404
You've deprecated std::min std::max. You can find instances by doing a search with grep. Or you can fiddle about with the headers themselves to break std::min, std::max. Or you can try defining min / max or std::min, std::max to the preprocessor. The latter is a bit dodgy because of C++ namespace, if you define std::max/min you don't pick up using namespace std, if you define min/max, you also pick up other uses of these identifiers.
Or if the project has a standard header like "mylibrary.lib" that everyone includes, break std::min / max in that.
The functions should return NaN when passed NaN, of course. But the natural way of writing them will trigger always false.
Upvotes: 1
Reputation: 153840
I don't think making standard library functions unavailable is the correct approach. First off, NaN are a fundamental aspect of how floating point value work. You'd need to disable all kinds of other things, e.g., sort()
, lower_bound()
, etc. Also, programmers are paid for being creative and I doubt that any programmer reaching for std::max()
would hesitate to use a < b? b: a
if std::max(a, b)
doesn't work.
Also, you clearly don't want to disable std::max()
or std::min()
for types which don't have NaN, e.g., integers or strings. So, you'd need a somewhat controlled approach.
There is no portable way to disable any of the standard library algorithms in namespace std
. You could hack it by providing suitable delete
d overloads to locate uses of these algorithms, e.g.:
namespace std {
float max(float, float) = delete; // **NOT** portable
double max(double, double) = delete; // **NOT** portable
long double max(long double, long double) = delete; // **NOT** portable
// likewise and also not portable for min
}
Upvotes: 32
Reputation: 234715
There's no portable way of doing this since, aside from a couple of exceptions, you are not allowed to change anything in std
.
However, one solution is to
#define max foo
before including any of your code. Then both std::max
and max
will issue compile-time failures.
But really, if I were you, I'd just get used to the behaviour of std::max
and std::min
on your platform. If they don't do what the standard says they ought to do, then submit a bug report to the compiler vendor.
Upvotes: 4