Допустимо ли передавать неарифметические типы в качестве аргументов для функций cmath?

Учитывая следующий определяемый пользователем тип S с функцией преобразования в double:

struct S
{
   operator double() { return 1.0;}
};

и следующие вызовы функций cmath с использованием типа S:

#include <cmath>

void test(S s) {
   std::sqrt(s); 
   std::log(s); 
   std::isgreater(s,1.0);
   std::isless(s,1.0);
   std::isfinite(s) ;
}

Этот код компилируется с помощью gcc с использованием libstdc++ (посмотреть вживую), но с clang при использовании libc++ возникают ошибки для нескольких вызовов (посмотреть вживую) со следующей ошибкой для isgreater:

error: no matching function for call to 'isgreater'
   std::isgreater(s,1.0);
   ^~~~~~~~~~~~~~

note: candidate template ignored: disabled by 'enable_if' [with _A1 = S, _A2 = double]
std::is_arithmetic<_A1>::value &&
^

и аналогичные ошибки для isless и isfinite, поэтому libc++ ожидает, что аргументы для этих вызовов будут арифметические типы, которые S не являются, мы можем подтвердить это, перейдя к источнику для заголовок libc++ cmath. Хотя требования к арифметическим типам не являются одинаковыми для всех функций cmath в libc++.

Итак, вопрос в том, допустимо ли передавать неарифметические типы в качестве аргументов функциям cmath?


person Shafik Yaghmour    schedule 10.06.2015    source источник


Ответы (1)


TL;DR

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

Подробнее

Заголовок cmath рассматривается в черновом стандартном разделе 26.8 [c. math] обеспечивает дополнительную перегрузку float и long double для каждой функции, определенной в math.h, который принимает аргумент double и, кроме того, параграф 11 обеспечивает достаточное количество перегрузок и говорит :

При этом должны быть предусмотрены дополнительные перегрузки, достаточные для обеспечения:

  1. Если какой-либо аргумент, соответствующий параметру double, имеет тип long double, то все аргументы, соответствующие параметрам double, эффективно преобразуются в тип long double.
  2. В противном случае, если какой-либо аргумент, соответствующий параметру double, имеет тип double или целочисленный тип, то все аргументы, соответствующие параметрам double, фактически преобразуются в тип double.
  3. В противном случае все аргументы, соответствующие параметрам типа double, фактически преобразуются в число с плавающей запятой.

Это похоже на C++11

В разделе C++11 26.8 [c.math] нет никаких ограничений, запрещающих неарифметические аргументы для cmath функций. В каждом случае из вопроса у нас есть доступная перегрузка, которая принимает аргумент(ы) double, и их следует выбирать через разрешение перегрузки.

Отчет о дефекте 2086

Но для C++14 у нас есть отчет о дефекте 2086: слишком универсальная поддержка типов для математических функций., в котором утверждается, что первоначальная цель раздела 26.8 [c.math] состояла в том, чтобы ограничить cmath функций допустимыми только для арифметических типов, что будет имитировать то, как они работали на C:

У меня сложилось впечатление, что этот набор правил, вероятно, является более общим, как предполагалось, я предполагаю, что он написан для имитации набора правил C99/C1x в 7.25 p2+3 способом "C++" [...] (обратите внимание, что ограничения C допустимый набор для типов, которые C++ описывает как арифметические типы, но см. ниже одно важное отличие) [...]

и говорит:

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

и изменена формулировка раздела 26.8 параграфа 11 (выделено мной):

При этом должны быть предусмотрены дополнительные перегрузки, достаточные для обеспечения:

  1. Если какой-либо арифметический аргумент, соответствующий параметру double, имеет тип long double, то все арифметические аргументы, соответствующие параметрам типа double, фактически преобразуются в тип long double.
  2. В противном случае, если какой-либо арифметический аргумент, соответствующий параметру double, имеет тип double или целочисленный тип, то все арифметические аргументы, соответствующие параметрам типа double, эффективно преобразуются в тип double.
  3. В противном случае все арифметические аргументы, соответствующие параметрам типа double, фактически преобразуются в эффективно приводятся к типу float.

Значит, это неверно в C++14?

Что ж, несмотря на намерение, технически это выглядит все еще действительным, как утверждается в этом комментарии из обсуждения в отчет об ошибке libc++: некорректная реализация isnan и подобных функций:

Это могло быть намерением, но я не вижу никакого способа прочитать формулировку стандарта таким образом. Из примера в комментарии №0:

std::isnan(A());

Здесь нет аргументов арифметического типа, поэтому ни один из пунктов 26.8/11 не применим. Набор перегрузок содержит «isnan(float)», «isnan(double)» и «isnan(long double)», и следует выбрать «isnan(float)».

Таким образом, переформулировка DR 2086 параграфа 11 не делает неправильным называть float, double и long double< /em> перегружает доступные в противном случае неарифметические аргументы.

Технически допустимо, но сомнительно в использовании

Таким образом, хотя стандарт C++11 и C++14 не ограничивает cmath функций арифметическими аргументами, DR 2068 утверждает, что цель 26.8 параграфа 11 заключалась в том, чтобы ограничить cmath функциями принимать только арифметические аргументы и, по-видимому, предназначалась для закрытия лазейки в C++. 14, но не предусматривал достаточно сильных ограничений.

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

std::isgreater( static_cast<double>(s) ,1.0)
                ^^^^^^^^^^^^^^^^^^^^^^

Как указывает Potatoswatter, использование унарного + также является вариантом:

std::isgreater( +s ,1.0)

Обновить

Как Т.С. указывает в C++11, можно утверждать, что 26.8 абзац 11 маркер 3 применим, поскольку аргумент не является ни long double, double, ни целым числом, и поэтому аргументы тип S должен быть сначала приведен к типу float. Обратите внимание, как указано в отчете о дефекте, gcc никогда не реализовывал это, и, насколько мне известно, clang тоже.

person Shafik Yaghmour    schedule 10.06.2015
comment
Возможно, формулировка C++11 требует, чтобы s приводилось к float, поскольку его тип не является ни long double, ни double, ни целочисленным типом. - person T.C.; 10.06.2015
comment
@Т.С. Я продолжал спорить об этом, но да, это спорно. Обратите внимание, что ни libc++, ни libstdc++ не реализуют это таким образом. - person Shafik Yaghmour; 10.06.2015
comment
+ s тоже можно считать идиоматическим. - person Potatoswatter; 11.06.2015
comment
@Potatoswatter, это справедливо, я лично предпочитаю использовать более явное приведение, чем возвращаться к использованию унарного + или приведения в старом стиле C, но я знаю, что многие будут утверждать, что это экономит ввод и не так уродливо. - person Shafik Yaghmour; 11.06.2015