potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

WIL GitHub Wiki ビット操作ヘルパーの和訳・改変

MicrosoftがGitHubでMITライセンス公開しているWILのGitHub Wikiからビット操作ヘルパーの記事を和訳・改変したものです。素人による翻訳なので、誤訳や著作権上の問題などありましたらご連絡いただけますと幸いです。

ビット演算ヘルパー

Duncan Horn、2020/7/17、リビジョン4個

ビット演算ヘルパー

wil/common.hでビット演算の可読性改善とエラー率減少のために多数のヘルパーマクロを定義しています。

これらのマクロは操作を簡潔に表すことで可読性を改善します。フラグと変数はマクロの呼び出し毎に指定され、マクロの名前は操作の特性を説明します。

また、複雑なビット操作を1ステップにまとめることでエラーを減少させます。単一フラグ操作はコンパイル時に単一フラグだけが指定されたことを検証します。

例:

// 次のコードの代わり:days = days & ~DaysOfWeek::Monday;
WI_ClearFlag(days, DaysOfWeek::Monday);

// 次のコードの代わり:if ((days & (DaysOfWeek::Monday | DaysOfWeek::Tuesday)) == (DaysOfWeek::Monday | DaysOfWeek::Tuesday))
if (WI_AreAllFlagsSet(days, DaysOfWeek::Monday | DaysOfWeek::Tuesday))

フラグ操作

以下の表で使います。

  • varは変数です。
  • flagコンパイル時定数で2の指数(単一フラグ)です。 この要件はコンパイル時に実施されます。
  • flagsmasknewFlags は実行時の値またはコンパイル時定数です。0から複数個のフラグで構成できます。
  • condは論理型(boolean)の条件式です。

マクロは記述のようにvarの値を変更します。

マクロ 操作
WI_SetFlag(var, flag); 単一ビットを設定します。
WI_SetFlagIf(var, flag, cond); condtrueなら単一ビットを設定します。
WI_SetAllFlags(var, flags); 0から複数個のビットを設定します。
WI_ClearFlag(var, flag); 単一ビットをクリアします。
WI_ClearFlagIf(var, flag, cond); condtrueなら単一ビットをクリアします。
WI_ClearAllFlags(var, flags); 0から複数個のビットをクリアします。
WI_UpdateFlag(var, flag, cond); condtrueなら単一ビットを設定して、condfalseなら単一ビットをクリアします。
WI_UpdateFlagsInMask(var, mask, newFlags); maskで指定された部分フラグをnewFlagsに対応するビットに変換します。
WI_ToggleFlag(var, flag); 単一ビットをトグル(XOR)します。
WI_ToggleAllFlags(var, flags); 0から複数個のビットをトグル(XOR)します。

フラグ検査

以下の表で使います。

  • valは式です。
  • flagコンパイル時定数で2の指数(単一フラグ)です。この要件はコンパイル時に施行されます。
  • flagsmasknewFlags は実行時の値またはコンパイル時定数です。0から複数個のフラグで構成できます。

マクロは記述のようにvalの値をテストして、条件に一致すればtrue、一致しなければfalseを返します。

マクロ 条件
WI_IsFlagSet(val, flag) flagで指定したビットがvalに設定されている。
WI_IsAnyFlagSet(val, flags) flagsで指定したビットのいずれかがvalに設定されている。
WI_AreAllFlagsSet(val, flags) flagsで指定したビットのすべてがvalに設定されている。
WI_IsFlagClear(val, flag) flagで指定したビットがvalに設定されていない。
WI_IsAnyFlagClear(val, flags) flagsで指定したビットのいずれかがvalに設定されていない。
WI_AreAllFlagsClear(val, flags) flagsで指定したビットのすべてがvalに設定されていない。
WI_IsSingleFlagSet(val) valは単一ビットだけ設定されている。
WI_IsSingleFlagSetInMask(val, mask) valmaskで指定したビットだけ設定されている。
WI_IsClearOrSingleFlagSet(val) valは高々1ビットが指定されている。
WI_IsClearOrSingleFlagSetInMask(val, mask) valmaskで指定したビットの高々1ビットが指定されている。

enum class DaysOfWeek
{
    None      = 0x0000,
    Sunday    = 0x0001,
    Monday    = 0x0002,
    Tuesday   = 0x0004,
    Wednesday = 0x0008,
    Thursday  = 0x0010,
    Friday    = 0x0020,
    Saturday  = 0x0040,
};
DEFINE_ENUM_FLAG_OPERATORS(DaysOfWeek);

extern DaysOfWeek GetAvailableDays();

DaysOfWeek days = DaysOfWeek::Sunday;

WI_SetFlag(days, DaysOfWeek::Saturday);
// 結果:days = Sunday | Saturday

WI_ClearFlag(days, DaysOfWeek::Sunday);
// 結果:days = Saturday

WI_SetAllFlags(days, DaysOfWeek::Monday | DaysOfWeek::Tuesday);
// 結果:days = Monday | Tuesday | Saturday

WI_SetFlagIf(days, DaysOfWeek::Friday, false);
// 結果:days = Monday | Tuesdy | Saturday (unchanged)

WI_ToggleFlag(days, DaysOfWeek::Monday);
// 結果:days = Tuesday | Saturday

WI_UpdateFlag(days, DaysOfWeek::Monday, false);
// 結果:days = Tuesday | Saturday

// Mondayを設定してTuesdayをクリアします。Wednesdayは影響しません。
WI_UpdateFlagsInMask(days, DaysOfWeek::Monday | DaysOfWeek::Tuesday,
                           DaysOfWeek::Monday | DaysOfWeek::Wednesday);
// 結果:days = Monday | Saturday

if (WI_IsFlagSet(days, DaysOfWeek::Sunday)) ... // false

if (WI_AreAllFlagsSet(days, GetAvailableDays()) ...

if (WI_AreAllFlagsClear(days, GetAvailableDays()) ...

// 単一ビットだけ設定されているかテストします。
if (WI_IsSingleFlagSet(days)) ... // false

次の使い方はコンパイルできないでしょう。

WI_SetFlag(days, DaysOfWeek::None); // エラー

// 代わりにUse WI_SetAllFlags insteadを使います。
WI_SetFlag(days, DaysOfWeek::Thursday | DaysOfWeek::Friday); // エラー

// 代わりにWI_SetAllFlagsを使います。
WI_SetFlag(days, GetAvailableDays()); // エラー

if (WI_IsFlagSet(days, DaysOfWeek::None)) // エラー

// エラー
// 求める結果によりWI_AreAllFlagsSetまたはWI_IsAnyFlagSetを使います。
if (WI_IsFlagSet(days, DaysOfWeek::Sunday | DaysOfWeek::Saturday)) // エラー

他のヘルパー

WI_EnumValue

WI_EnumValueマクロは与えられた列挙値(enumの値)と幅および値の同じ符号なし整数を返します。これはenumのフィールドがその他の整数の組み合わせに一致するか検証するときに便利です。

enum class DropEffects
{
    None = 0x0000,
    Copy = 0x0001,
    Move = 0x0002,
    Link = 0x0004,
};

static_assert(WI_EnumValue(DropEffects::None) == DROPEFFECT_NONE);
static_assert(WI_EnumValue(DropEffects::Copy) == DROPEFFECT_COPY);
static_assert(WI_EnumValue(DropEffects::Move) == DROPEFFECT_MOVE);
static_assert(WI_EnumValue(DropEffects::Link) == DROPEFFECT_LINK);

WI_StaticAssertSingleBitSet

値が正確に2の指数でない場合にコンパイラにエラーを強制します。元の値を返します。このマクロは典型的に他のビット操作マクロで使用されます。

if (effect & WI_StaticAssertSingleBitSet(DropEffects::Copy))
{
   ...
}

著作権表示

この記事は以下の著作物を使用しています。

Copyright (c) Microsoft Corporation. All rights reserved. https://github.com/microsoft/wil/blob/master/LICENSE