10

新しいC++11ヘッダーを使用したVisualC++ 2012での動作が、VC ++2010での使用とは異なるサンプルコードがいくつかあります。これは、cmathをインクルードしたときに取得するstd::fmod関数を呼び出すとどうなるかに関するものです。渡す引数がdoubleではなく、double演算子への暗黙の変換を持つクラスである場合:

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

驚いたことに、これは2つのdoubleをとるfmodのバージョンではなく、2つのfloatをとるfmodのバージョンを呼び出します。

だから私の質問は、これはC ++ 11標準を与えられた正しい振る舞いですか?動作について私が見つけることができる唯一の情報は、ここのcppreference.comドキュメントにあります。

いずれかの引数が整数型の場合、doubleにキャストされます。他の引数がlongdoubleの場合、戻り型はlong doubleになり、それ以外の場合はdoubleになります。

ただし、Visual Studioヘッダーファイルでの実装は、「それ以外の場合はフロート」を実装しているように見えます。

誰もが意図が何であるかを知っています:-)?

GCCのオンラインc++11バージョンで例を実行すると(GCCの最近のコピーに簡単にアクセスすることはできません)、fmodの「ダブル」バージョンを呼び出しているように見えます。これは私が素朴に期待していることです。 。

明確にするために、私は使用しています

Microsoft(R)C /C++最適化コンパイラバージョン17.00.51106.1forx86

付属しているものです

Microsoft Visual Studio Express 2012 forWindowsDesktopバージョン11.0.51106.01Update1

4

2 に答える 2

5

これは私のこの質問に関連しています。その理由は、標準で要求される (および質問で引用されている) 追加のオーバーロードを提供するために、VS 2012 はすべての 2 引数数学関数の一般的な関数テンプレートを定義するためです。つまり、実際に呼び出すのではfmod(float, float)なくfmod<Num>(Num, Num)、そもそも呼び出します。

このテンプレート化された関数がプレーンdoubleバージョンよりも優先される理由は、ダブル バージョンでは からNumへのユーザー定義の変換が必要になるためですdouble。一方、テンプレート バージョンは直接インスタンス化可能です。

しかし、関数を呼び出す実際の基本型fmodは、次の型特性によって決定されます<xtgmath.h>

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れた型(あなたの場合は再びNumNum明らかにそうではない整数かどうかのみをチェックするため)をチェックすることです。そうでなければの場合float

この誤った動作の理由は、これらの追加の数学オーバーロードがすべての型に対して提供されることを意図したものではなく、組み込みの算術型に対してのみ提供されることを意図していたためです (そして、あいまいな標準の文言は、への私の回答で述べたように、修正されようとしています。リンクされた質問)。したがって、VS 2012 では、上記で説明したすべての型推論メカニズムで、渡された型が組み込みの整数型であるか、そうでない場合は組み込みの浮動小数点型であると想定していますNum。したがって、実際の問題は、VSがあまりにも一般的な数学関数を提供するのに対し、組み込み型のオーバーロードのみを提供する必要があることです(1引数の関数に対して既に適切に行われているように)。リンクされた回答で述べたように、私はすでにこれについてバグを報告しました。

EDIT: 実際には(あなたも気づいたように)現在あいまいな標準の文言に従い、ジェネリック関数テンプレートを提供する必要がある場合でも、それらのジェネリック引数の実際の昇格された型を のdouble代わりに定義する必要がありfloatます。しかし、ここでの実際の問題は、この型変換プロセス全体で非組み込み型の存在の可能性を完全に無視していることだと思います (組み込み型のロジックは完全に正常に機能するため)。

しかし、セクション26.8 [c.math]の現在のあいまいな標準的な文言 (ただし、既に変更が計画されています) によると、昇格された型をfloat(3 番目のケースに従って) 正しく推測しています。

以下を保証するのに十分な追加のオーバーロードが必要です。

  1. double パラメーターに対応する引数の型が long double である場合、double パラメーターに対応するすべての引数は実質的に long double にキャストされます。
  2. それ以外の場合、double パラメーターに対応するいずれかの引数が double 型または整数型である場合、double パラメーターに対応するすべての引数は実質的に double にキャストされます。
  3. それ以外の場合、double パラメーターに対応するすべての引数は実質的に float にキャストされます。
于 2013-03-22T14:57:01.627 に答える
0

クリスチャンがリンクされた質問と回答で指摘したように、これは標準を厳密に読むことが要求する動作です。

ただし、すべてのバージョンでこれを非常に簡単に回避できます。

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));
}
于 2013-03-22T15:04:32.107 に答える