*** ВНИМАНИЕ: Блог переехал на другой адрес - demin.ws ***

воскресенье, 15 февраля 2009 г.

std::min() и std::max() в Visual Studio

Простейший кусок кода (файл minmax.cpp):
#include <algorithm>
int main() {
int a = std::min(10, 20);
return 0;
}
Все тривиально и отлично компилируется и в Visual Studio, и в CodeGear/Borland Studio, и Cygwin. Но допустим потребовались какие-то функции из Windows API, и вы подключили файл windows.h:
#include <algorithm>
#include <windows.h>
int main() {
int a = std::min(10, 20);
return 0;
}
Теперь компиляция в Visual Studio (я проверял в 2005 и 2008) будет падать со следующей ошибкой:
minmax.cpp
minmax.cpp(4) : error C2589: '(' : illegal token on right side of '::'
minmax.cpp(4) : error C2059: syntax error : '::'
Постановка #include <windows.h> до #include <algorithm> проблемы не решает.
Очевидно, проблема в том, что кто-то переопределил значение слова min. Запустим препроцессор и проверим догадку:
cl /P minmax.cpp
И что мы видим? А видим мы следующее (фрагмент файла minmap.i):
#line 7 "minmax.cpp"
int main() {
int a = std::(((10) < (20)) ? (10) : (20));
return 0;
}
Естественно, это каша с точки зрения синтаксиса, и компилятор ругается совершенно законно.

Покопавшись в заголовочных файлах Windows SDK, в файле WinDef.h, который косвенно подключается через windows.h, я нашел корень зла:

#ifndef NOMINMAX

#ifndef max
#define max(a,b) (((a) > (b)) ? (a) : (b))
#endif

#ifndef min
#define min(a,b) (((a) < (b)) ? (a) : (b))
#endif

#endif /* NOMINMAX */
Вот теперь ясно, что делать — надо определить макрос NOMINMAX, тем самым заблокировать определение min и max:
#define NOMINMAX
#include <algorithm>
#include <windows.h>
int main() {
int a = std::min(10, 20);
return 0;
}
Вот теперь в Visual Studio все нормально компилируется.

Забавно, что в Cygwin и CodeGear/Borland исходный пример компилируется без проблем. В борландовой версии windows.h я нашел вот такой фрагмент:

#if defined(__BORLANDC__)
...
# if defined(__cplusplus)
# define NOMINMAX /* for WINDEF.H */
...
# endif
...
#endif /* __BORLANDC__ */
Эдак они заранее оградились от проблемы, принудительно запретив проблемные макросы.

Вывод: Порой промежуточные результаты работы препроцессора являются крайне полезной информацией.

На всякий случай напомню, как его запускать для перечисленных мной компиляторов:

Visual Studio:

cl /P имя_исходника.cpp
Borland/CodeGear Studio:
cpp32 имя_исходника.cpp
Cygwin:
cpp имя_исходника.cpp
Прочие флаги командной строки должны повторять флаги при обычной компиляции. Для препроцессора важны определения макросов (обычно это флаги -D и -U) и пути для поиска включаемых файлов (обычно это флаг -I).


Другие посты по теме:

2 комментария:

  1. Тут есть один интересный тонкий момент - бороться с этим без #define NOMINMAX/#undef max можно вот так - (std::max)(a, b). Подсмотрено кажется где-то в бусте.

    ОтветитьУдалить
  2. Arseny Kapoulkine: Действительно, если написать(std::max)(a, b), то макроподстановка не происходит, и все работает.

    Спасибо.

    ОтветитьУдалить