Почему sqrt() отлично работает с переменной int, если она не определена для int?

В главе 3 книги Programming: Principles and Practice with C++ (шестое издание) Stroustrup заявляет (стр. 68): "Обратите внимание, что sqrt() не определено для int".

Вот простая программа на C++, основанная на этой главе:

#include "std_lib_facilities.h"

int main()
{
    int n = 3;
    cout << "Square root of n == " << sqrt(n) << "\n";
}

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

К моему удивлению, его компиляция (с помощью g++ (GCC) 4.2.1) и запуск прошли успешно без ошибок или предупреждений и выдали следующий вполне приличный результат:

Square root of n == 1.73205

Поэтому мой вопрос: если sqrt() действительно не определено для int, то почему вышеприведенная программа почему-то не дает сбой?


person Community    schedule 26.10.2013    source источник
comment
Вы делаете то, что он сказал, не зная об этом. Ваш int неявно преобразуется в double. Я думаю, что он рекомендует явно использовать double, чтобы избежать путаницы такого рода.   -  person BoBTFish    schedule 30.10.2013
comment
Ваше программное обеспечение выполняет неявное преобразование. В качестве руководства я всегда рекомендую избегать неявных преобразований — если функция принимает double, передайте ей именно double и ничего больше.   -  person Daniel Daranas    schedule 30.10.2013
comment
@Max Потому что я не могу утруждать себя объяснением неявных преобразований типов и перегрузки функций на уровне детализации, необходимом для того, чтобы сделать то, что я считаю хорошим ответом, или выполнить фоновую проверку, чтобы убедиться, что я прав в отношении того, какие преобразования происходят . Если кто-то еще хочет потратить это время или опубликовать половинчатый ответ, это его дело.   -  person BoBTFish    schedule 30.10.2013
comment
См. раздел 3.9.1 Безопасные преобразования, с. 79.   -  person Adam Burry    schedule 30.10.2013
comment
Тесно связанное обсуждение здесь: stackoverflow. com/questions/5563000/   -  person Adam Burry    schedule 30.10.2013
comment
@AndreyT Я не думаю, что ответ на дубликаты правильный, поскольку существует несколько перегрузок, и с явным регистром для целочисленных значений это было бы неоднозначно.   -  person Shafik Yaghmour    schedule 30.10.2013
comment
@BoBTFish есть три перегрузки с плавающей запятой, без явной перегрузки, которая охватывает целочисленный случай, это было бы неоднозначно.   -  person Shafik Yaghmour    schedule 30.10.2013
comment
@ShafikYaghmour Правильно, извините. Я все еще имею привычку предполагать, что у людей нет C++ поддержки - мы здесь в застойном состоянии C++03 :(   -  person BoBTFish    schedule 30.10.2013
comment
@Max И, как оказалось, я ошибался, не потратив времени на тщательные размышления!   -  person BoBTFish    schedule 30.10.2013
comment
@BOBTFish Похоже, тогда ответ был бы лучше, так как тогда люди могли бы понизить его, или вы могли бы отредактировать его, чтобы он был более правильным.   -  person Max    schedule 30.10.2013
comment
Большое спасибо за помощь! Очень хороший урок для новичка.   -  person user2936478    schedule 30.10.2013
comment
@Shafik Yaghmour: Вы правы, но, как вы правильно отметили в своем ответе, новая спецификация предназначена для сохранения устаревшего кода, т. Е. Аргументы int должны каким-то образом разрешаться в double перегрузки, не вызывая ошибки неоднозначности перегрузки.   -  person AnT    schedule 30.10.2013
comment
Этот вопрос был объединен с другим точным дубликатом, я обновил свой ответ, чтобы он был более конкретным и, насколько я могу судить, более правильным.   -  person Shafik Yaghmour    schedule 10.12.2013
comment
Возможный дубликат sqrt() типа int в C   -  person luator    schedule 20.02.2019


Ответы (6)


Обновление 2

Этот вопрос был объединен с точной копией, если взглянуть на это, фактический ответ намного проще, чем кто-либо изначально думал. Текущая версия std_lib_facilities.h включает следующую строку:

inline double sqrt(int x) { return sqrt(double(x)); }   // to match C++0x

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

Если std_lib_facilities.h не использовался, исходная логика по-прежнему применяется, хотя gcc-4.2 довольно старая по сравнению с Visual Studio 2012 из исходного вопроса, но 4.1.2 версия использует __builtin_sqrt специально для целочисленного случая.

Исходный

Поскольку примерно в 2005 проект стандарта требовал, чтобы аргумент integer был преобразован в double, это описано в проект стандарта C++. Если мы посмотрим в раздел 26 Библиотека числовых значений, а затем перейдем к разделу 26.8 Библиотека C, который охватывает заголовок <cmath>, он определяет перегрузки математических функций для float, double и long double, которые описаны в параграфе 8:

В дополнение к двойным версиям математических функций в C++ добавлены версии этих функций с плавающей точкой и длинной двойной перегрузкой с той же семантикой.

что было бы ambiguous для случая int, но стандарт требует, чтобы была предоставлена ​​достаточная перегрузка, чтобы целочисленные аргументы преобразовывались в двойные. Это описано в параграфе 11, в котором говорится (выделено мной):

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

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

Обновить

Как указывает @nos, возможно, что вызываемая версия sqrt взята из заголовка math.h, а не из перегрузок из cmath, если это так, и, вероятно, здесь есть определенная реализация, то мы может возвращаться к старому поведению в стиле C, если доступна только версия sqrt(double), что означает, что int будет неявно преобразован в double .

Один из способов проверить это на gcc и clang — это использовать тип long для a, который вместе с флагом -Wconversion вызывает предупреждение о конверсии, потенциально изменяющей значение, на моей платформе, если у нас есть только sqrt(double). Действительно, если я включу math.h вместо cmath, мы сможем выдать это предупреждение. Хотя я не могу вызвать это поведение в clang, что, по-видимому, указывает на то, что это зависит от реализации.

person Shafik Yaghmour    schedule 30.10.2013
comment
В текущем стандарте соответствующий раздел — [cmath.syn]/2 Для каждого набора перегруженных функций... - person M.M; 09.05.2018

10 неявно преобразуется в двойное. Это произойдет автоматически, если у вас есть правильный прототип функции для sqrt.

Изменить: избит комментариями

person tangrs    schedule 30.10.2013
comment
Есть три перегрузки с плавающей запятой, которые были бы неоднозначными и ошибочными, поэтому должна быть явная перегрузка для целочисленных случаев. - person Shafik Yaghmour; 30.10.2013
comment
@Shafik Yaghmour: Вы правы, но, как вы правильно заметили в своем ответе, новая спецификация предназначена для сохранения устаревшего кода, т. Е. Аргументы int должны каким-то образом разрешаться в double перегрузки, не вызывая ошибки неоднозначности перегрузки. - person AnT; 30.10.2013
comment
@nos Это может быть возможно, если math.h используется вместо cmath, но если используется cmath, то я не думаю, что это будет иметь место, но это, вероятно, определяется реализацией, позвольте мне изменить свой ответ, чтобы охватить оба случая. Хороший вопрос, спасибо, что указали на возможность. - person Shafik Yaghmour; 30.10.2013

Из-за неявных преобразований. sqrt определяется для double, а значение int может быть (и преобразуется) неявно в значение типа double.

(На самом деле довольно сложно предотвратить вызов функции, которая принимает double, с int. Вы можете заставить свой компилятор выдать предупреждение, но поскольку обычно это преобразование с сохранением значения, даже это может быть сложно. C++ наследует от C дизайн, чтобы изо всех сил стараться заставить код работать, даже если это требует искажений. Другие языки гораздо строже в отношении таких вещей.)

person Kerrek SB    schedule 26.10.2013
comment
Что касается вашего второго абзаца, не будет ли достаточно просто добавить перегрузку sqrt(int) = delete? Я знаю, что скоро появится упоминание о C++11, так как это будет работать иначе, чем просто не определять его в pre-C++11, кроме более приятной ошибки? - person chris; 27.10.2013
comment
@chris: я говорил в более общем плане. Конечно, вы можете добавить отдельные перегрузки, но чтобы охватить все арифметические типы, которые неявно конвертируются друг в друга, вы запутаетесь. (Рассмотрите возможность использования нескольких аргументов.) - person Kerrek SB; 27.10.2013
comment
Хорошая мысль о нескольких параметрах, хотя после небольшого тестирования один параметр, кажется, работает довольно хорошо со всеми встроенными целочисленными типами. - person chris; 27.10.2013

sqrt определяется для double. А C++ позволяет неявно преобразовывать int в double.

int n = 3;
double x = sqrt(n);    // implicit conversion of n from int to double

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

Примером для второго случая может быть:

int n = 3;
double x = n;          // implicit conversion of n from int to double

Обратите внимание, что операторы также являются просто функциями. Таким образом, вы также можете добавить int к double, что преобразует int в double перед вызовом фактического добавления:

int n = 3;
double x = 1.0;
double sum = n + x;    // implicit conversion of n from int to double
person leemes    schedule 26.10.2013

Потому что есть неявное преобразование из int в double.

С преобразованием ваш код будет выглядеть так:

cout << "Square root of n == " << sqrt((double)n) << "\n";
person xorguy    schedule 26.10.2013
comment
Вы имеете в виду cout << "Square root of n == " << sqrt(double(n)) << "\n"; ? - person ; 27.10.2013
comment
@sampablokuper, оба делают одно и то же. - person chris; 27.10.2013
comment
@sampablokuper Я написал преобразование с синтаксисом C, вы написали его с синтаксисом C++ (я просто привык к первой форме). - person xorguy; 27.10.2013
comment
@xorguy, я бы не стал называть это формой С++, учитывая, что в С++ также есть static_cast и др. (которые рекомендуются вместо обоих). Правильное название — функциональный (функциональный) состав. - person chris; 27.10.2013
comment
@chris, спасибо за разъяснения :) Сейчас я просматриваю cplusplus.com/doc/ учебник/приведение типов - person ; 27.10.2013

Потому что компилятор на самом деле автоматически (то есть «неявно») преобразует целое число в double (или, может быть, long double) и отправляет это значение в sqrt(). Это совершенно нормально и совершенно законно.

person yzt    schedule 26.10.2013