(AB) と (BA) の結果を unsigned にキャストし、さらに でマスク (bitwise-and) し(sizeof(int) - 1)
ます。これにより、GCC 5.5 および 6.3 の警告がクリアされます。GCC の最近のバージョンでは、警告は生成されません。
template <int A, int B> void f(int X) {
// ...
if (A >= B)
{
SetValue(X << ((unsigned)(A-B) & (sizeof(int) - 1)));
}
else // (A < B)
{
SetValue(X >> ((unsigned)(B-A) & (sizeof(int) - 1)));
}
}
未定義の動作に関するさまざまなコメントに対処するために注意
してください。この提案されたソリューションが未定義の動作を引き起こす可能性がある唯一の意味は、オペランドのビット幅よりも大きな量のシフトを実行することです。ただし、これは比較によって保護されます。A と B の差が安全なシフト カウントであると仮定すると、これは問題で暗示されているため、if (A >= B)
その量のシフトのみが実際に実行されることが保証されます。if
ステートメントのもう一方のブランチは実行されないため、シフトを実行せず、シフトから未定義の動作を生成することはできません (ただし、実行された場合は確実に実行されます)。
何人かのコメンターが、実行されていないブランチが依然として未定義の動作を引き起こす可能性があると主張しています。このような誤解がどのように発生するのかについて、私はやや途方に暮れています。次のコードを検討してください。
int *a = nullptr;
if (a != nullptr) {
*a = 4;
}
これで、null ポインターの逆参照が実行されなくても未定義の動作を引き起こす場合、ガード条件は役に立たなくなります。これは明らかにそうではありません。上記のコードはまったく問題ありません。a
の値を割り当て、ガードのためにnullptr
逆参照しません。a
このような明白な例 (null への代入の直後に null のチェックが続く) は実際のコードでは発生しない傾向がありますが、一般に「保護された逆参照」は一般的なイディオムです。実際にチェックされたポインターが null の場合、それ自体が未定義の動作を生成することはありません。それがガードが役立つ理由です。