2013-03-22 56 views
10

我有一些示例代碼,在Visual C++ 2012下使用新的C++ 11頭文件的行爲與在VC++ 2010下的行爲不同。 它涉及當調用的std :: FMOD功能,你得到當你包括CMATH會發生什麼,當你傳遞參數不雙打,但相當有一個隱式轉換到雙操作類:fmod(和其他)的不同行爲在Visual Studio中至少有

#include <cmath> 

class Num { 
double d_; 
public: 
Num(double d) : d_(d) {} 

operator double() const { return d_; } 
}; 

int main(int argc, char* argv[]) { 
Num n1(3.14159265358979323846264338327950288419716939937510); 
Num n2(2.0); 

double result1 = fmod((double)n1, (double)n2); 
double result2 = fmod((float)n1, (float)n2); 
double result3 = fmod(n1, n2); 

if (result2==result1) std::cout << "fmod(double, double) returns the same as fmod(float,float)" << std::endl; 
if (result3==result1) std::cout << "fmod(Num, Num) returns the same as fmod(double,double)" << std::endl; 
if (result3==result2) std::cout << "fmod(Num, Num) returns the same as fmod(float,float)" << std::endl; 
} 

更令人驚訝的是,這會調用帶有兩個浮點數的fmod版本,而不是使用兩個雙精度浮點數的fmod版本。

所以我的問題是,這是給C++ 11標準的正確行爲?我能找到的行爲的唯一信息是cppreference.com文檔here,它說(重點煤礦)中:

如果任何參數有整型,它被轉換爲加倍。如果任何其他參數是長雙倍,那麼返回類型是長雙倍,,否則它是雙倍

但是,在Visual Studio頭文件中的實現似乎實現「否則它是一個浮動」。

任何人都知道這個意圖是什麼:-)?

通過在線C++ 11版本的GCC運行該示例(我沒有簡單訪問GCC的最新副本,否則)它似乎是調用fmod的「雙」版本,它是我天真地期待的。

爲了清楚起見,我使用

微軟(R)C/C++優化編譯器版本17.00.51106.1用於x86

這就是自帶

的Microsoft Visual Studio Express 2012 for Windows Desktop版本11.0.51106.01更新1

+0

你怎麼證明它是調用'float'版本? – NPE 2013-03-22 14:27:34

+0

我通過它調試了一下。我還沒有一個例子證明它正在做什麼並且將會更新哪一種方式。 – 2013-03-22 14:43:19

+0

太好了。我認爲看到SSCCE(http://sscce.org/)會很有幫助,並且還有你的編譯器版本的全部細節。 – NPE 2013-03-22 14:46:08

回答

5

這與this question of mine有關。原因是,爲了提供標準所要求的額外重載(並在你的問題中引用),VS 2012爲所有2參數數學函數定義了通用函數模板。所以你實際上並沒有打電話fmod(float, float),而是首先打fmod<Num>(Num, Num)

此模板函數優於普通double版本是,因爲雙版本將需要一個用戶定義的轉換從Numdouble,而模板版本是直接實例化的原因。

但實際的基本類型,呼籲被此類型的特徵從<xtgmath.h>確定fmod功能:

template<class _Ty> 
    struct _Promote_to_float 
    { // promote integral to double 
    typedef typename conditional<is_integral<_Ty>::value, 
     double, _Ty>::type type; 
    }; 

template<class _Ty1, 
    class _Ty2> 
    struct _Common_float_type 
    { // find type for two-argument math function 
    typedef typename _Promote_to_float<_Ty1>::type _Ty1f; 
    typedef typename _Promote_to_float<_Ty2>::type _Ty2f; 
    typedef typename conditional<is_same<_Ty1f, long double>::value 
     || is_same<_Ty2f, long double>::value, long double, 
     typename conditional<is_same<_Ty1f, double>::value 
      || is_same<_Ty2f, double>::value, double, 
      float>::type>::type type; 
    }; 

這樣做是檢查促進型_Promote_to_float(而你的情況又是Num,因爲它只檢查它的整數,其中Num顯然不是)與所有浮點類型相匹配,直到匹配,否則結果爲float

這種錯誤行爲的原因是,那些額外的數學重載從來不是爲每種類型提供的,而僅僅是爲內置算術類型提供的(並且如上所述,ambigous標準措辭即將修復在我對答案的回答中)。因此,在上面解釋的所有這種類型演繹機制中,VS 2012假定傳入的類型是內置整型類型,或者如果不是,則內置浮點類型,這當然不適用於Num。所以實際的問題是,VS提供了過於通用的數學函數,而它們只應該爲內建類型提供重載(比如已經爲1-argument函數正確完成)。正如鏈接的答案中所述,我已經爲此提交了一個錯誤。

編輯:事實上(因爲你也意識到),即使它們將遵循目前ambigous規範的寫法,並提供通用的函數模板是必需的,他們應該還是有定義的實際推動型的通用參數作爲double而不是float。但我認爲這裏的實際問題是,他們完全忽略了在整個類型轉換過程中可能存在的非內建類型(因爲內建類型的邏輯工作非常好)。

但是根據章節當前歧義標準的措辭(這是已經計劃改變,雖然)26.8 [c.math]他們確實正確地推導出提升的類型爲float(每第三情況):

應再重載足以確保:

  1. 如果對應於一個雙參數的任何參數的類型爲長雙,則對應於雙參數所有參數都是 有效投下長雙。
  2. 否則,如果任何對應於double參數的參數具有double型或整數型,則對應於雙參數的所有參數將被有效地轉換爲double。
  3. 否則,與雙參數相對應的所有參數都將有效地轉換爲浮點型。
+0

正如您在回答其他問題時所說的那樣,這種不良行爲似乎是標準所要求的。 – 2013-03-22 15:00:32

+0

但是這個bug是不是會在最後一行使用「float」而不是「double」?我的意思是在cppreference.com頁面中的單詞說「否則它是雙重的」,而Visual C++實現「否則它是一個浮點數」。因此,我問什麼是「正確的」行爲 – 2013-03-22 15:08:21

+0

@BenVoigt但我也指出一個已經批准的標準修復,修復此問題。但是,即使採取了目前含糊不清的標準措辭,它也需要「雙重」版本來表達通用的論點。 – 2013-03-22 15:10:17

0

正如Christian在他的相關問答中指出的那樣,這是嚴格閱讀標準要求的行爲。

但是可以解決這個適用於所有版本很容易:

Num fmod(const Num a, const Num b) 
{ 
    const double cvt_a = a; 
    const double cvt_b = b; 
    return Num(fmod(cvt_a, cvt_b)); 
} 
相關問題