Есть ли способ предотвратить использование разработчиками std:: min, std:: max?

У нас есть библиотека алгоритмов, выполняющая множество операций std::min/std::max для чисел, которые могут быть NaN. Учитывая этот пост: Почему выпуск /Debug имеет другой результат для std:: min?, мы поняли, что это явно небезопасно.

Есть ли способ запретить разработчикам использовать std::min/std::max?

Наш код скомпилирован как с VS2015, так и с g++. У нас есть общий заголовочный файл, содержащий все исходные файлы (через /FI для VS2015 и -include для g++). Есть ли какой-либо фрагмент кода/прагмы, который может быть помещен сюда, чтобы сделать какой-либо файл cpp с помощью std::min или std::max не скомпилирован?

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

Ответ 1

Я не думаю, что предоставление стандартных функций библиотеки недоступно, это правильный подход. Во-первых, NaN являются фундаментальным аспектом работы с плавающей точкой. Вам нужно будет отключить всевозможные другие вещи, например sort(), lower_bound() и т.д. Кроме того, программистам платят за творчество, и я сомневаюсь, что любой программист, достигший для std::max(), не решался использовать a < b? b: a если std::max(a, b) не работает.

Кроме того, вы явно не хотите отключать std::max() или std::min() для типов, которые не имеют NaN, например целых чисел или строк. Таким образом, вам потребуется несколько контролируемый подход.

Не существует переносного способа отключения любого из стандартных алгоритмов библиотеки в пространстве имен std. Вы можете взломать его, предоставив подходящие перегрузки delete d, чтобы найти применение этих алгоритмов, например:

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
}

Ответ 2

Я собираюсь немного философствовать здесь и меньше кода. Но я думаю, что лучшим подходом было бы обучение этих разработчиков и объяснение, почему они не должны кодироваться определенным образом. Если вы сможете дать им хорошее объяснение, то они не только прекратят использовать функции, которые вы им не хотите. Они смогут распространять сообщение другим разработчикам в команде.

Я считаю, что заставить их просто заставить их придумать работу вокруг.

Ответ 3

Поскольку изменение std запрещается, следующим является UB, но может работать в вашем случае. Пометка функции как устаревшая:

Так как С++ 14, устаревший атрибут:

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&));
}

Демо

И до

#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&));

}

Демо-версия

Ответ 4

Нет никакого переносимого способа сделать это, поскольку, кроме нескольких исключений, вам не разрешается ничего менять в std.

Однако одно решение -

#define max foo

прежде чем включать какой-либо из вашего кода. Затем обе std::max и max будут выдавать ошибки времени компиляции.

Но на самом деле, если бы я был вами, я бы привык к поведению std::max и std::min на вашей платформе. Если они не выполняют то, что стандарт говорит, что они должны делать, то отправьте отчет об ошибке поставщику компилятора.

Ответ 5

Если вы получаете разные результаты в отладке и выпуске, проблема не в том, чтобы получить разные результаты. Проблема в том, что одна версия, или, возможно, и то, и другое, неверны. И это не устраняется путем отказа от std:: min или std:: max или замены их различными функциями, которые определили результаты. Вы должны выяснить, какой результат вы действительно хотите для каждого вызова функции, чтобы получить правильный результат.

Ответ 6

Я не буду точно отвечать на ваш вопрос, но вместо того, чтобы вообще отказаться от std::min и std::max, вы могли бы обучить своих коллег и убедиться, что вы последовательно используете общий компаратор заказов вместо необработанного operator< (неявно используется многими стандартными библиотечными алгоритмами) всякий раз, когда вы используете функцию, которая полагается на данный порядок.

Такой компаратор предлагается для стандартизации в P0100 - Сравнение в С++ (а также частичные и слабые компараторы порядка), возможно, нацеленные на C + +20. Между тем, стандартный комитет С довольно долго работал над TS 18661 - расширения с плавающей запятой для C, часть 1: двоичная арифметическая с плавающей запятой, видимо, нацеливается на будущее C2x (должно быть ~ C23), которое обновляет заголовок <math.h> множеством новых функций, необходимых для реализации недавнего ISO/IEC/IEEE 60559: стандарт 2011 года. Среди новых функций есть totalorder (раздел 14.8), который сравнивает числа с плавающей запятой в соответствии с IEEE totalorder:

totalOrder (x, y) налагает полное упорядочение на канонические элементы формата x и y:

  • Если x < y, totalOrder (x, y) истинно.
  • Если x > y, totalOrder (x, y) является ложным.
  • Если x = y
    • totalOrder (-0, +0) истинно.
    • totalOrder (+0, -0) - false.
    • Если x и y представляют одну и ту же точку с плавающей запятой:
      • Если x и y имеют отрицательный знак, totalOrder (x, y) истинно тогда и только тогда, когда показатель степени x ≥ показатель степени y.
      • В противном случае totalOrder (x, y) истинно тогда и только тогда, когда показатель степени x ≤ показатель степени y.
  • Если x и y неупорядочены численно, потому что x или y является NaN:
    • totalOrder (-NaN, y) истинно, где -NaN представляет собой NaN с отрицательным знаковым битом, а y - число с плавающей запятой.
    • totalOrder (x, + NaN) истинно, где + NaN представляет собой NaN с положительным знаковым битом, а x - число с плавающей запятой.
    • Если x и y - оба NaN, то totalOrder отражает полное упорядочение на основе:
      • отрицательные знаковые заказы ниже положительного знака
      • порядки сигнализации ниже тихих для + NaN, обратные для -NaN
      • меньшая полезная нагрузка, если она рассматривается как целое число, порядка ниже большей полезной нагрузки для + NaN, обратная для -NaN.

Это довольно стена текста, вот список, который помогает увидеть, что больше, чем (от большего к меньшему):

  • положительные спокойные NaNs (упорядоченные по полезной нагрузке, считающиеся целыми)
  • положительная сигнализация NaNs (упорядоченная по полезной нагрузке, рассматриваемая как целое)
  • положительная бесконечность
  • положительные переменные
  • положительный ноль
  • отрицательный ноль
  • отрицательные операции
  • отрицательная бесконечность
  • отрицательная сигнализация NaNs (упорядоченная по полезной нагрузке, рассматриваемая как целое)
  • отрицательные тихие NaNs (упорядоченные по полезной нагрузке, считающиеся целыми)

К сожалению, этому общему порядку в настоящее время не хватает поддержки библиотеки, но, вероятно, можно взломать собственный общий компаратор заказов для чисел с плавающей запятой и использовать его всякий раз, когда вы знаете, что будут сравнивать числа с плавающей запятой. Как только вы получите такой общий компаратор, вы можете безопасно использовать его везде, где это необходимо, вместо простого отказа от std::min и std::max.

Ответ 7

Если вы скомпилируете GCC или Clang, вы можете отравить эти идентификаторы.

#pragma GCC poison min max atoi /* etc ... */

Использование их приведет к ошибке компилятора:

error: attempt to use poisoned "min"

Единственная проблема с этим в С++ заключается в том, что вы можете только отражать токены идентификатора, а не std::min и std::max, поэтому на самом деле также отравляет все функции и локальные переменные по именам min и max... может быть, не совсем то, что вы хотите, но, возможно, не проблема, если вы выберете Good Descriptive Variable Names ™.

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

Например,

#define strrchr rindex
#pragma GCC poison rindex
strrchr(some_string, 'h');

не приведет к ошибке.

Прочтите ссылку для получения дополнительной информации, конечно.

https://gcc.gnu.org/onlinedocs/gcc-3.3/cpp/Pragmas.html

Ответ 8

Вы устарели std:: min std:: max. Вы можете найти примеры, выполнив поиск с помощью grep. Или вы можете играть с заголовками сами, чтобы разбить std:: min, std:: max. Или вы можете попытаться определить min/max или std:: min, std:: max для препроцессора. Последнее немного изворотливое из-за пространства имен С++, если вы определяете std:: max/min, который вы не забираете с использованием пространства имен std, если вы определяете min/max, вы также можете использовать другие виды использования этих идентификаторов.

Или, если у проекта есть стандартный заголовок, такой как "mylibrary.lib", который каждый включает, перерыв std:: min/max в этом.

Функции должны возвращать NaN при передаче NaN, конечно. Но естественный способ их записи всегда будет всегда ложным.