Предотвратяване на неявни преобразувания от float в double в C++

По принцип, ако искам нещо подобно,

double b = sin(2.2);

но случайно напишете нещо подобно,

double b = sin(2.2f);

няма съобщение за грешка или дори предупредително съобщение, въпреки че това очевидно води до различен, неточен и следователно неправилен резултат. Този тип грешка може да бъде предотвратена, като се принуди компилаторът да не прави косвени преобразувания на float в double. Има ли някакъв начин да се постигне това, било то чрез превключвател за компилация (за предпочитане във Visual Studio), някои интелигентни макроси или клас, който се държи като променливи float/double и декларира свои собствени оператори?

Редактиране: Също така се интересувам от решаване на подобни проблеми с помощта на оператори (като например двойно b = 2.2f*2.2f) или присвоявания (двойно b=2.2f).


person user3768612    schedule 29.06.2014    source източник
comment
Въпреки че такъв клас може лесно да бъде написан, той никога няма да работи за стандартни библиотечни функции с double параметри, когато подадете float стойност: 2.2f винаги има тип float, никога персонализиран клас. Тъй като изброявате потребителски клас като опция, можете ли да редактирате въпроса си, за да изясните в кои случаи искате грешка и в кои не?   -  person    schedule 29.06.2014
comment
Този клас ще има някои изрични функции за преобразуване от и в плаващи или двойни стойности, така че това може да се използва за претоварване на стандартни библиотечни функции. Мисля, че това не би трябвало да е твърде много работа на практика, тъй като би изглеждало по същия начин (и следователно може да бъде поставено в макрос) за много стандартни библиотечни функции. Знаете ли за такъв клас? Тогава няма да има нужда да го пиша сам... По принцип искам грешка при всяко имплицитно преобразуване между float и double (и ints, ако е възможно, но това е по-малко важно).   -  person user3768612    schedule 29.06.2014
comment
защо това е проблем? просто поставете регулярен израз във вашия скрипт за изграждане, който проверява за необработени плаващи литерали.   -  person TemplateRex    schedule 29.06.2014


Отговори (4)


Можете да използвате помощна програма type_assert.

Пример:

#include <cmath>
#include <type_traits>

template<typename T, typename U>
const U& type_assert(const U& u) {
    static_assert(std::is_same<T, U>::value, "...");
    return u;
}

int main() {
    double t = type_assert<double>(std::sin(2.2f));
}

Ако очакваният тип е различен, това ще ви даде грешка на компилатора. Вероятно компилаторът вероятно би могъл да оптимизира това, ако премине, поне в моя случай с -O3.

person Rapptz    schedule 29.06.2014
comment
Ако го направихте constexpr, това ще бъде доста хубава помощна програма. - person Kerrek SB; 29.06.2014
comment
Това прави кода по-малко четлив за по-сложни операции, но използването на макроси не би трябвало да е лошо. Благодаря, ще го имам предвид! - person user3768612; 29.06.2014

Можете да направите извикването на sin(float) двусмислено, като дефинирате нещо като

double sin(float x) { abort(); }

Тогава извикванията на sin на float ще ви дадат грешка по време на компилиране. Това работи, защото в заглавката cmath std::sin е претоварена функция с float sin(float), double sin(double) и long double sin(long double) варианти. Компилаторът не може да знае дали сте искали ::sin(float) или std::sin(float), така че вдига ръце в объркване.

Не бих препоръчал непременно да оставяте подобни неща във вашата кодова база, но може да бъде полезно, ако се опитвате да намерите всички случаи на грешка като тази и да ги коригирате.

person tmyklebu    schedule 29.06.2014
comment
Това би помогнало само за извиквания на функции, а не за присвоявания или оператори, но би решило тази част от проблема доста добре! - person user3768612; 29.06.2014
comment
@user3768612: Да. Отново, мислете за това като за диагностичен инструмент, а не като полезна техника. Това е като да маркирате отхвърлен член на структура или клас като „частен“, само за да видите кой го извиква. - person tmyklebu; 29.06.2014

Нещо такова?

class MyDouble{
public:
  MyDouble(float _f) {throw "only use double!";}
  MyDouble(double _d) : m_data(_d) {}

  operator float() const {throw "conversion to float occurred!";}
  operator double() const {return m_data;}

private:
  double m_data;
};

но да, ще трябва да започнете да използвате този клас във вашите извиквания на математически функции, което го прави много грозен:

MyDouble d(3.2);
sin(d);
// or...
cos(MyDouble(2.3));

АКТУАЛИЗАЦИЯ - пример за внедряване на оператор (двоичен оператор, тъй като унарният би трябвало да бъде част от класа):

MyDouble operator+(const & MyDouble _lhs, const & MyDouble _rhs){
  MyDouble rez(_lhs);
  rez += _rhs;
  return rez;
}

Разбира се, не е нужно да го прилагате по този начин (чрез използването на пряк път operator+=), но това прави кода по-лесен за поддръжка

person YePhIcK    schedule 29.06.2014
comment
Да, нещо подобно би свършило работа. Извикванията на математически функции вероятно биха могли да бъдат претоварени (и така или иначе не ми трябват толкова много от тях), така че това не е голям проблем. Но искам и оператори...знаете ли дали някой го е правил вече? - person user3768612; 29.06.2014
comment
Ако говориш за тривиални математически оператори - те са много лесни за изпълнение - person YePhIcK; 30.06.2014
comment
Наличието на грешка по време на компилиране или връзка за MyDouble(float) изглежда по-добре от грешка по време на изпълнение...? - person Tony Delroy; 30.06.2014

Току-що разбрах, че използвате продукт на Microsoft, но ще оставя този отговор, тъй като може да помогне на някой друг.

Прегледахте ли превключвателите на компилатора? Предполагам, че има подобен вариант.

БОГ! Току-що потърсих помощ за опциите и предупрежденията на Visual Studio! (връзка)

МНОГО съжалявам! Не знаех! Това е един от най-малко полезните списъци с опции и превключватели, които някога съм виждал! Още по-иронично, те са започнали да търсят отговори в StackOverflow и да се свързват обратно тук.

Но намерих някои улики:

Предупреждение на компилатора (нива 3 и 4) C4244 - преобразуване" преобразуване от " type1" до "type2", възможна загуба на данни

Използва се по този начин:

// Enable this to find unintended double to float conversions.
// warning C4244: 'initializing' : conversion from 'double' to 'float', possible loss of data
#pragma warning(3 : 4244)

GCC/G++ превключватели (

Защо не позволите на компилатора да ви каже, когато случайно направите това?

От g++/gcc man страницата:

-Wdouble-promotion (C, C++, Objective-C and Objective-C++ only)
  * Give a warning when a value of type "float" is implicitly promoted to "double".
    ... some verbage clipped ...
  * It is easy to accidentally do computations with "double" because floating-point
    literals are implicitly of type "double".

Добавянето на -Wdouble-promotion към вашия CXXFLAGS трябва да ви предостави предупреждения по време на компилиране.

Има също -fsingle-precision-constant, което забранява имплицитното преобразуване на плаващи числа в двойни.

person lornix    schedule 30.06.2014
comment
Е, пу! Изглежда, че конкретното предупреждение (C4244) наистина е за намиране на narrowing операции, от които float -› double не е, тъй като това е widening операция. Драт. - person lornix; 30.06.2014
comment
Може да използвам GCC или Clang в бъдеще, така че и това е полезно да знам. За съжаление няма налична среда за разработка за GCC или Clang, която да има същото ниво на функционалност като Visual Studio, така че за много хора Visual Studio и неговият компилатор са по-добрият вариант, като се имат предвид всички неща. - person user3768612; 16.07.2014