Распознать нестандартный С++ переносимо?

C имеет __STDC__, но, похоже, не существует стандартного способа распознавания некоторых расширенных диалектов C++. Следовательно, для переносимого кода я использую

#define __is_extended                                   \
    ((__GNUG__   &&!__STRICT_ANSI__)  ||                \
     (_MSC_VER   && _MSC_EXTENSIONS && __cplusplus)  || \
     (__IBMCPP__ && __EXTENDED__))

Пока это работает для gcc, XLC и Visual C++.

Мы должны тестировать соответствие ISO/ANSI идиосинкразически для каждого компилятора, верно? Если да, можете ли вы предложить другие компиляторы, которые доказали свою эффективность?

РЕДАКТИРОВАТЬ: Поскольку было так много дискуссий о «за» и «против» таких тестов, вот реальный пример. Скажем, есть некоторый заголовок stuff.h, широко используемый несколькими компиляторами в нескольких проектах. stuff.h использует некоторые специфичные для компилятора vsnprintf (не стандартизованные до C++11), некоторые copy_if<> (они как-то пропустили это в C++98), собственные охранники мьютексов и не только. При реализации чистого варианта C++11 вы оборачиваете старую (но доверенную) реализацию в какой-то #if __is_extended (лучше: __is_idosyncratic или !__is_ANSI_C11). Новый C++11 идет за #else. Когда единица перевода, которая по-прежнему компилируется как C++0x или C++98, включает stuff.h, ничего не меняется. Никаких ошибок компиляции, никакого другого поведения во время выполнения. C++11 остается экспериментальным. Код можно безопасно коммитить в основную ветку, коллеги могут изучать его, учиться на нем и применять техники со своими компонентами.


person Andreas Spindler    schedule 19.07.2015    source источник
comment
что вы получаете от такого макроса? нестандартные расширения различаются от компилятора к компилятору, поэтому вы знаете только, что активированы некоторые расширения.   -  person m.s.    schedule 19.07.2015
comment
Я не понимаю вопроса. Можете ли вы привести конкретный пример того, что бы вы сделали с результатом __is_extended?   -  person Christian Hackl    schedule 19.07.2015
comment
Убедитесь, что определенные единицы перевода не могут использовать некоторые языковые расширения. Включите утверждения времени компиляции, такие как #if __is_extended #error this is portable code #endif, или, возможно, макросы, такие как #define __is_ANSI_CPP11 (__cplusplus == 201103L && !__is_extended).   -  person Andreas Spindler    schedule 19.07.2015
comment
@AndreasSpindler: Но чем это лучше, чем простой вызов компилятора с флагами строгого соответствия? Например, _MSC_EXTENSIONS будет определено, если вы используете флаг /Ze. Решение этой проблемы состоит в том, чтобы использовать не /Ze, а /Za. Почему вы сначала включили расширения в компиляторе, а затем обработали собственную конфигурацию компилятора как ошибку?   -  person Christian Hackl    schedule 19.07.2015
comment
@Christian: Да, в целом ты прав. Затем бывают ситуации, когда вы хотите протестировать единицы перевода, файлы заголовков, функции или даже строки кода, если они действительно соответствуют. Например, в больших проектах, когда вам приходится иметь дело с большим количеством (исторически выросшего) кода, написанного многими людьми, такие ограничения помогают. Например, при (1) переходе на C++ 11 и обеспечении того, чтобы вещи, которые были расширены до C++, теперь соответствовали или (2) коде переноса из Windows в UNIX и сначала сделайте его настоящим C++. Я не утверждаю, что такой макрос, как __is_extended, является общеупотребительным, но во время разработки он может быть полезен.   -  person Andreas Spindler    schedule 19.07.2015
comment
@AndreasSpindler: я понимаю проблему, но я не понимаю, как компиляция исторически разросшегося кода с помощью /Za и то, что ваш компилятор точно указывает на нестандартные фрагменты кода через полученные сообщения об ошибках, не решить это более элегантно.   -  person Christian Hackl    schedule 19.07.2015
comment
Скажем так: иногда вы не можете изменить настройки проекта, Makefiles и т.д. Я также понимаю, что __is_extended какой-то грязный. #pragma тоже грязный. Но опять же, что грязного в грязной среде? Я фрилансер уже почти 20 лет. Многие компании не платят деньги за чистый код — им нужна только функциональность. И некоторые люди, которых вы встречаете в проектах, которые делают C++, на самом деле не понимают его. До сих пор нередко просто оборачивать кучу функций C в struct и называть это классом.   -  person Andreas Spindler    schedule 19.07.2015
comment
Определение вышеуказанного токена делает вашу программу неправильной по стандарту. Я также не вижу, как вы могли бы использовать его с пользой.   -  person Yakk - Adam Nevraumont    schedule 19.07.2015
comment
@AndreasSpindler: вы не сможете #if __is_extended #error this is portable code #endif скомпилировать без изменения настроек проекта. Даже если код внутри на самом деле является переносимым. И это не поможет вам обнаружить, просто оберните кучу функций C структурой и назовите ее классом. Итак, какова цель?   -  person Ben Voigt    schedule 19.07.2015
comment
Это просто макрос, и поэтому он отличается от настроек проекта. Неизвестные макросы расширяются до 0 по умолчанию. Что касается цели: я добавил пример в свой исходный пост. Однако я согласен с тем, что использование макроса типа __is_extended в новом проекте было бы странным, возможно, поэтому в стандарте C++ нет __STDCPLUSPLUS__.   -  person Andreas Spindler    schedule 19.07.2015
comment
@Андреас, но у него есть __cplusplus!   -  person rubenvb    schedule 03.06.2016


Ответы (3)


Ваш вопрос на самом деле является обратным, потому что нестандартные расширения, поддерживаемые компилятором, специфичны для этого компилятора - часто в той степени, в которой они специфичны для конкретной версии компилятора, - как и нестандартные макросы, которые каждый компилятор определяет, чтобы их можно было обнаружить .

Обычный метод — обратный: укажите какую-либо функцию, которую вы хотите, свяжите ее с некоторым макросом и напишите код, который использует эту функцию, только если связанный макрос определен.

Скажем, есть какая-то странная функция, которая поддерживается точно так же Visual C++ 11 и g++ версии 3.2.1, но не какими-либо другими компиляторами (даже другими версиями Visual C++ или g++).

//  in some header that detects if the compiler supports all sorts of features    

#if ((defined(__GNUG__) && __GNUC__ == 3 && __GNUC_MINOR__ == 2 && __GNUC_PATCHLEVEL__ == 1) || (defined(_MSC_VER) && _MSC_VER == 1700))

#define FUNKY_FEATURE

#endif

// and, in subsequent user code ....

#ifdef FUNKY_FEATURE

  // code which uses that funky feature

 #endif

Существует множество свободно доступных библиотек общего назначения, которые используют этот метод (очевидно, с лучшими именами макросов). Одним из примеров, который приходит на ум, является фреймворк ACE (Adaptive Communication Environment), который имеет набор макросов переносимости, задокументированных здесь.

Использование таких макросов — работа не для слабонервных, если вас беспокоит большой набор нестандартных функций, учитывая, что необходимо понимать, какие версии каких компиляторов (или библиотек) поддерживают каждую функцию, и обновлять макросы каждый раз, когда выпускается новый компилятор, новая библиотека или даже патч.

Также необходимо избегать использования зарезервированных идентификаторов в именах этих макросов и обеспечивать уникальность имен макросов. Идентификаторы, начинающиеся с двойного подчеркивания, зарезервированы.

person Peter    schedule 19.07.2015

В общем, это будет сложно сделать, потому что, если вы полагаетесь на несовместимый компилятор, то нет стандартизированного способа требовать только стандартные правила (поведение нестандартного компилятора не определяется стандартом).

Что вы можете сделать, так это добавить дополнительный шаг сборки или перехватчик фиксации и передать код ТАКЖЕ через определенный переносимый компилятор (например, g++) с определенными параметрами строгого соответствия.

person 6502    schedule 19.07.2015
comment
Да, это работает для целых единиц перевода. Но вы должны изменить систему сборки. Если __is_extended проверки станут постоянными, я бы тоже этого предпочел. Но иногда гораздо быстрее использовать препроцессор. - person Andreas Spindler; 19.07.2015

Во-первых, вы не можете называть переменные таким образом (#define __is_extended), потому что имена, начинающиеся с двух символов подчеркивания, зарезервированы для реализации.

Метод, который у вас есть, по-прежнему зависит от компилятора и может дать сбой: кроме __cplusplus, ни один из этих макросов не является стандартным, и поэтому для их определения реализация не требуется. Более того, этот тест в основном проверяет, какой компилятор используется, а не используются ли какие-либо расширения используются.

Мой совет — просто не использовать расширения. В них очень-очень мало нужды. Если вы все же хотите убедиться, что они все равно не будут использоваться, вы можете включить флаги вашего компилятора, чтобы ограничить использование расширений; для GCC есть целая глава об этом в разделе "Параметры, управляющие диалектом C".

person edmz    schedule 19.07.2015
comment
(1) _MSC_EXTENSIONS, __STRICT_ANSI__ и __EXTENDED__ на самом деле определяются только в том случае, если используются некоторые расширения. (2) Да - начиная с С++ 11 расширения используются реже. Но существует много кода C++98, который будет медленно переноситься. (3) Даже gcc -Wall -Wextra -pedantic не жалуется на имена с символами подчеркивания, так что я думаю, что мы в безопасности :-) - person Andreas Spindler; 19.07.2015
comment
@AndreasSpindler 1) Их не обязательно определять. Кроме того, для gcc/clang пример кода с расширениями их не определяет. __STRICT_ANSI__ просто существует, если указано -ansi или -std. 3) Это УБ. Диагностика не требуется, и компилятор может делать с ней все, что захочет. См. это. - person edmz; 19.07.2015
comment
Я не понимаю, что вы имеете в виду, говоря, что они не обязаны определяться. Пока в руководстве сказано, что конкретный макрос определен для определенных параметров компилятора, мы можем его использовать, верно? Например, __STRICT_ANSI__ определяется, когда используются -ansi, -std=c++98, -std=c++11, но не -std=gnu++11. Это хорошо работает. Можно быстро протестировать что-то вроде touch empty.cpp; gcc -std=c++11 -dM -E empty.cpp | grep __STRICT. - person Andreas Spindler; 19.07.2015
comment
@AndreasSpindler Да, можешь. Но гарантировано ли его существование завтра? Нет. Гарантировано ли, что он будет определен в другой реализации? Нет. Обязательно ли это должно быть значимым? Нет. И так далее. Вы поняли идею. - person edmz; 19.07.2015
comment
...даже завтра не обязательно будет завтра ;-) Рано или поздно все изменится. Я обнаружил, что макросы компилятора меняются не так часто. Более вероятно, что ваша система сборки сломается или какой-то компонент среды выполнения (база данных, серверы) изменит поведение. - person Andreas Spindler; 19.07.2015
comment
@AndreasSpindler: _MSC_EXTENSIONS и __EXTENDED__ действительно определяются только в том случае, если используются некоторые расширения. Нет. Они определяются, если расширения включены. Они ничего не говорят вам о том, использует ли их ваш код. - person Ben Voigt; 19.07.2015
comment
Извините, мой английский был двусмысленным. на самом деле означало использование ключей компилятора. - person Andreas Spindler; 19.07.2015