Author: Chloé Lourseyre
While working on not-so-old code, I ended up with a strange compilation error in MS Visual Studio :
Here was the incriminated code :
//...
const T_LongType BIG_OFFSET = std::numeric_limits<T_LongType>::max() / 2;
//...
And here the errors :
1>MyFile.cpp(42): error C2589: '(' : illegal token on the right side of '::'
1>MyFile.cpp(42): error C2059: syntax error : '::'
1>MyFile.cpp(42): error C2059: syntax error : ')'
1>MyFile.cpp(42): error C2059: syntax error : ')'
It came with a bunch of warnings I won’t even bother writing here.
I took me a hella lot of time to find out what was wrong. The type T_LongType
was correctly defined (actually a typedef of long
) and I didn’t forget to include <limits>
.
Many of you may already know the culprit, and it’s the following line :
#include <windows.h>
Indeed, if we look into the code of this library, we can see a surprising piece of code :
#ifndef NOMINMAX
#ifndef max
#define max(a,b) (((a) > (b)) ? (a) : (b))
#ifndef min
#define min(a,b) (((a) < (b)) ? (a) : (b))
#endif /* NOMINMAX */
To my defense, the file in which the error occured did not include windows.h
. It was included by another header.
Explanation
The fact that windows.h
defines macros called min
and max
implies that, during the preprocessor phase of the compilation, every instances of the words min
and max
will be replaced.
This means that, after the preprocessing, instead of seeing this :
const T_LongType BIG_OFFSET = std::numeric_limits<T_LongType>::max() / 2;
The compiler will see this :
const T_LongType BIG_OFFSET = std::numeric_limits<T_LongType>::(((a) > (b)) ? (a) : (b))() / 2;
Which makes no sense, thus the compilation errors mentioned above.
Many reasons to not include windows.h
Here is a non-exhaustive list that why it is a bad practice to include this header :
- It breaks the standard library just by including it. The way we use the functionality of the standard library should work whatever the header files we include.
- It forces you to define
NOMINMAX
and the beginning of each headers that includewindows.h
. And if you ever happen to forget this, every file that will include your header will have to define it. - Since it’s OS-dependant, it’s better to avoid it as long as you can. If you use where you don’t need to, you won’t be able to port your code to other systems, you may obtain bad coding habits (by relying too much on it) and to not forget that the more specific a library is, the less maintained it will be.
Conclusion
All in all there are ways to use windows.h
safely, but you must be absolutely be solid in how you include it to avoid side-effects to other sources and headers.
As long as you can, don’t use it.
Author: Chloé Lourseyre
The best way to include Windows.h is to define WIN32_LEAN_AND_MEAN; NOSERVICE; NOMCX; NOIME;NOMINMAX; globally before including it, this ensures that the minimal header files are pulled in by Windows.h
The NOMINMAX problem is already very old. Microsoft will probably never make any big changes anymore to Windows.h, as it will break too many (legacy) apps.
They have started however a project to make Windows.h obsolete for modern C++ apps:
https://github.com/microsoft/cppwin32
This project is however very silent since its introduction.
Meh. Known for eons. This remnant from the 80s can’t be fixed because Hyrum’s Law. Whoever included Windows.h in the past 30 years without #define NOMINMAX before the #include directive is outride hostile to the community.