2013-03-22 6 views
10

Ich habe einige Beispielcode, der sich unter Visual C++ 2012 mit den neuen C++ 11-Header anders verhält als unter VC++ 2010 Es handelt sich darum, was passiert, wenn Sie die std :: fmod-Funktion aufrufen, die Sie erhalten, wenn Sie cmath einschließen, und wenn die übergebenen Argumente nicht doubles sind, sondern Klassen sind, die eine implizite Konvertierung zum doppelten Operator haben:Unterschiedliches Verhalten von fmod (und andere) unter C++ 11, in Visual Studio mindestens

#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; 
} 

Eher zu meiner Überraschung nennt dies die Version von Fmod, die zwei Floats statt der Version von fmod, die zwei Doppel nimmt.

Also meine Frage ist, ist dies das richtige Verhalten angesichts der C++ 11 Standard? Die einzige Information, die ich über das Verhalten finden kann, ist in der Dokumentation cppreference.com here, die sagt (Hervorhebung von mir):

Wenn ein Argument Integral-Typen hat, wird sie werfen zu verdoppeln. Wenn ein anderes Argument doppelt lang ist, ist der Rückgabetyp lang doppelt, andernfalls ist es doppelt.

Allerdings scheint die Implementierung in den Visual Studio-Header-Dateien zu implementieren "andernfalls ist es ein Float".

Jeder weiß, was die Absicht ist :-)?

Nachdem ich das Beispiel über eine Online-Version von C++ 11 von GCC ausgeführt habe (ich habe sonst keinen einfachen Zugriff auf eine aktuelle Kopie von GCC), würde es anscheinend die "doppelte" Version von fmod aufrufen was ich naiv erwarte.

Aus Gründen der Klarheit, ich bin mit

Microsoft (R) C/C++ Optimizing Compiler Version 17.00.51106.1 für x86

das, was mit

Microsoft Visual kommt, ist Studio Express 2012 für Windows Desktop Version 11.0.51106.01 Update 1

+0

Wie haben Sie feststellen, dass es die 'float' Version nennen? – NPE

+0

Ich habe einen Debugger durchlaufen. Ich habe kein Beispiel, das die eine oder andere Art beweist, was man tut, und wird aktualisiert. –

+0

Großartig. Ich denke, es wäre wirklich hilfreich, einen SSCCE (http://sscce.org/) zu sehen und auch vollständige Details Ihrer Compiler-Version zu haben. – NPE

Antwort

5

Dies steht im Zusammenhang mit this question of mine. Der Grund dafür ist, dass VS 2012 allgemeine Funktionsvorlagen für alle mathematischen Funktionen mit zwei Argumenten definiert, um die zusätzlichen Überlastungen zu bieten, die vom Standard gefordert werden (und in Ihrer Frage zitiert werden). Sie rufen also nicht fmod(float, float) sondern fmod<Num>(Num, Num) an erster Stelle.

Der Grund dieser templated Funktion über die Ebene double Version bevorzugt wird, ist, weil die Doppel-Version eine benutzerdefinierte Umwandlung von Numdouble zu, während die Schablonenversion direkt instanzierbare ist erforderlich machen würde.

Aber der eigentliche Grundtypus nennt die fmod Funktion dann durch diese Art Charakterzug von <xtgmath.h> bestimmt:

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

Was ist den geförderten Typen _Promote_to_float (die wiederum in Ihrem Fall nicht überprüft Num ist, weil es nur überprüft, ob sein Integral, das Num eindeutig nicht ist, auf alle Gleitkommatypen, bis es übereinstimmt, was es nicht tut und so in den anderen Fall von float führt. Der Grund für dieses fehlerhafte Verhalten ist, dass diese zusätzlichen mathematischen Überladungen niemals für jeden Typ vorgesehen werden sollten, sondern nur für die eingebauten arithmetischen Typen (und der ambigous Standardwortlaut steht wie gesagt fest) in meiner Antwort auf die verbundene Frage). Bei all dieser Art von Ableitungsmechanik, wie oben erklärt, geht VS 2012 davon aus, dass die übergebenen Typen entweder in ganzzahligen Typen eingebaut sind oder, wenn nicht, dann eingebaute Gleitkommatypen, was natürlich für Num fehlschlägt. Das eigentliche Problem besteht also darin, dass VS zu generische mathematische Funktionen bereitstellt, während sie nur für integrierte Typen Überladungen bereitstellen sollten (wie es bereits für die 1-Argument-Funktionen richtig gemacht wurde). Wie in der verknüpften Antwort angegeben, habe ich bereits einen Fehler dafür eingereicht.

EDIT: In der Tat (wie man auch erkannt), auch wenn sie der zur Zeit mehrdeutig Standardformulierung folgen würden und generische Funktionsschablonen Bereitstellung erforderlich war, sollen sie immer noch die tatsächliche geförderte Art dieses generischen Arguments als double definiert haben anstelle von float. Aber ich denke, das eigentliche Problem hier ist, dass sie das mögliche Vorhandensein von nicht-eingebauten Typen in diesem ganzen Typenkonvertierungsprozess vollständig ignorieren (da für eingebaute Typen ihre Logik völlig in Ordnung ist).

Aber nach dem aktuellen mehrdeutig Standardformulierung (die bereits obwohl geplant zum Ändern) in Abschnitt 26,8 [c.math] sie gefördert Typ als float herzuleiten (pro 3. Fall) in der Tat richtig sind:

es werden zusätzliche Überlastungen ausreichen, um sicherzustellen, dass:

  1. Wenn ein Argument in einen doppelten Parameter entsprechend langen Doppel-Typ hat, dann werden alle Argumente Doppel Parameter entsprechen, sind effektiv zu lange verdoppeln.
  2. Andernfalls, wenn ein Argument, das einem Doppelparameter entspricht, den Typ double oder integer hat, werden alle Argumente, die double-Parametern entsprechen, effektiv zu double umgewandelt.
  3. Andernfalls werden alle Argumente, die doppelten Parametern entsprechen, effektiv in Float umgewandelt.
+0

Wie Sie in Ihrer Antwort auf die andere Frage gesagt haben, scheint dieses unerwünschte Verhalten vom Standard gefordert zu sein. –

+0

Aber scheint der Fehler nicht die Verwendung von "float" auf der letzten Zeile, anstatt "doppelt" zu sein? Ich meine die Wörter in der cppreference.com Seite sagen "andernfalls ist es ein Doppeltes", während Visual C++ "sonst ist es ein Floss" implementieren. Daher frage ich, welches war das "richtige" Verhalten –

+0

@BenVoigt Aber ich wies auch auf eine bereits genehmigte Standard-Fix, die dies beheben. Aber selbst wenn man den gegenwärtig zweideutigen Standardwortlaut nimmt, würde es die "doppelte" Version für die generischen Argumente erfordern. –

0

Als Christian in seiner verknüpften Frage und Antwort darauf hingewiesen hat, ist dies das Verhalten eine enge Auslegung der Norm fordert.

Sie können aber ganz einfach für alle Versionen dieses Problem umgehen:

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)); 
} 
Verwandte Themen