g ++ 4.9.0 -O2 -std = c ++ 11
template<class T>
struct vec3 {
T x, y, z;
vec3() = default;
vec3(const vec3<T> &other) = default;
vec3(T xx, T yy, T zz) { x = xx; y = yy; z = zz; }
vec3<T> operator-(const vec3<T> &other) {
return vec3<T>{ x - other.x, y - other.y, z - other.z };
}
};
int main() {
vec3<char> pos{ 0, 0, 0 };
vec3<char> newPos{ 0, 0, 0 };
auto p = pos - newPos;
return 0;
}
警告が表示されます:
!!warning: narrowing conversion of ‘(((int)((vec3<char>*)this)->vec3<char>::x) - ((int)other.vec3<char>::x))’ from ‘int’ to ‘char’ inside { } [-Wnarrowing]
しかし、(...)
insted of {...}
内operator-
関数は警告が消えます。どうして?
まず、なぜ狭めますか?それは§5/ 10から来ています:
算術型または列挙型のオペランドを期待する多くの二項演算子は、変換を引き起こし、同様の方法で結果の型を生成します。目的は、結果の型でもある共通の型を生成することです。このパターンは、通常の算術変換と呼ばれ、次のように定義されます。
— [..]
—それ以外の場合、積分プロモーション(4.5)は両方のオペランドで実行されます。
ここで、統合プロモーションは4.5/1で定義されています。
整数変換ランク(4.13)が
bool
のランクよりも小さいint
、_char16_t
_、_char32_t
_、または_wchar_t
_以外の整数型のprvalueは、型のprvalueに変換できますint
は、int
がソースタイプのすべての値を表すことができる場合。それ以外の場合、ソースprvalueは、タイプ_unsigned int
_のprvalueに変換できます。
この場合、decltype(char + char)
はint
です。これは、char
の変換ランクがint
よりも小さいため、両方が_operator+
_の呼び出し前にint
に昇格されるためです。これで、int
sを受け取るコンストラクターに渡すchar
sができました。定義により(§8.5.4/ 7、具体的には7.4):
縮小変換は暗黙的な変換です
(7.4)—整数型または対象範囲外の列挙型から、元の型のすべての値を表すことができない整数型まで。ただし、ソースが整数型昇格後の値がターゲット型に適合する定数式である場合を除く。
これは、§8.5.4/ 3に従ってリスト初期化で明示的に禁止されています(強調は、「下を参照」は実際に上記でコピーしたものを指します)。
T
型のオブジェクトまたは参照のリスト初期化は、次のように定義されます— [..]
—それ以外の場合、
T
がクラス型の場合、コンストラクターが考慮されます。該当するコンストラクターが列挙され、最適なコンストラクターがオーバーロード解決(13.3、13.3.1.7)によって選択されます。 引数のいずれかを変換するために縮小変換(以下を参照)が必要な場合、プログラムの形式は正しくありません。 [...]
_vec3<T>{int, int, int}
_が警告を出すのはこのためです。整数の昇格によりすべての式で変換を絞り込む必要があるため、プログラムの形式が正しくありません。現在、「不正な形式」に関する記述は、具体的にはリストの初期化のコンテキストでのみ発生します。 _{}s
_を指定せずにベクトルを初期化した場合、その警告は表示されないのはこのためです。
_vec3<T> operator-(const vec3<T> &other) {
// totally OK: implicit conversion from int --> char is allowed here
return vec3<T>( x - other.x, y - other.y, z - other.z );
}
_
この問題の解決に関しては、リストの初期化を行わずにコンストラクタを呼び出すだけがおそらく最も簡単な解決策です。または、リストの初期化を引き続き使用して、コンストラクターをテンプレート化することもできます。
_template <typename A, typename B, typename C>
vec3(A xx, B yy, C zz)
: x(xx) // note these all have to be ()s and not {}s for the same reason
, y(yy)
, z(yy)
{ }
_
ここでいくつかのことが行われています。まず、{...}
構文は、暗黙的な縮小変換を禁止します。したがって、簡単な解決策は、中括弧を括弧に変更することです。
vec3<T> operator-(const vec3<T> &other) {
return vec3<T>( x - other.x, y - other.y, z - other.z );
}
2番目に起こっているのは、「えー?charからcharを引いたものはcharです、問題は何ですか?!」そして、ここでの答えは、C/C++は算術演算に自然サイズを使用したいということです。だからこそ、(int)
エラーメッセージにキャストします。 良い説明があります なぜそうするのか(StackOverflowの答えが消えてしまった場合に備えて、彼はC11標準の6.3.1.1を引用しています)。
したがって、コードを修正する別の方法は次のとおりです。
vec3<T> operator-(const vec3<T> &other) {
return vec3<T>{
static_cast<char>(x - other.x),
static_cast<char>(y - other.y),
static_cast<char>(z - other.z)
};
}
ちなみに、Effective Modern C++の項目7は、()
を使用して初期化することをお勧めします。また、{}
優れている。時々、あなたはただ肩をすくめて他のものを使わなければなりません。