[解決済み] C++で保守性が高く、高速でコンパイル可能なビットマスクを書くにはどうしたらいいですか?
質問
以下のようなコードを持っています。
#include <bitset>
enum Flags { A = 1, B = 2, C = 3, D = 5,
E = 8, F = 13, G = 21, H,
I, J, K, L, M, N, O };
void apply_known_mask(std::bitset<64> &bits) {
const Flags important_bits[] = { B, D, E, H, K, M, L, O };
std::remove_reference<decltype(bits)>::type mask{};
for (const auto& bit : important_bits) {
mask.set(bit);
}
bits &= mask;
}
Clang >= 3.6
は賢明にもこれをコンパイルして、単一の
and
命令にコンパイルします (その後、他のすべての場所でインライン化されます)。
apply_known_mask(std::bitset<64ul>&): # @apply_known_mask(std::bitset<64ul>&)
and qword ptr [rdi], 775946532
ret
しかし
私が試した GCC のすべてのバージョン
はこれをコンパイルして、静的に DCE されるべきエラー処理を含む巨大な混乱に陥らせます。他のコードでは、それはさらに
important_bits
と同等のものをデータとしてコードに並べてしまいます!
.LC0:
.string "bitset::set"
.LC1:
.string "%s: __position (which is %zu) >= _Nb (which is %zu)"
apply_known_mask(std::bitset<64ul>&):
sub rsp, 40
xor esi, esi
mov ecx, 2
movabs rax, 21474836482
mov QWORD PTR [rsp], rax
mov r8d, 1
movabs rax, 94489280520
mov QWORD PTR [rsp+8], rax
movabs rax, 115964117017
mov QWORD PTR [rsp+16], rax
movabs rax, 124554051610
mov QWORD PTR [rsp+24], rax
mov rax, rsp
jmp .L2
.L3:
mov edx, DWORD PTR [rax]
mov rcx, rdx
cmp edx, 63
ja .L7
.L2:
mov rdx, r8
add rax, 4
sal rdx, cl
lea rcx, [rsp+32]
or rsi, rdx
cmp rax, rcx
jne .L3
and QWORD PTR [rdi], rsi
add rsp, 40
ret
.L7:
mov ecx, 64
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:.LC1
xor eax, eax
call std::__throw_out_of_range_fmt(char const*, ...)
このコードをどのように書けば、両方のコンパイラが正しい処理を行えるようになりますか?そうでない場合、どのように書けば、明確で、高速で、保守しやすいコードを維持できるでしょうか?
どのように解決するのですか?
最適なバージョンは c++17 :
template< unsigned char... indexes >
constexpr unsigned long long mask(){
return ((1ull<<indexes)|...|0ull);
}
次に
void apply_known_mask(std::bitset<64> &bits) {
constexpr auto m = mask<B,D,E,H,K,M,L,O>();
bits &= m;
}
戻る c++14 に戻ると、このような奇妙なトリックができるのです。
template< unsigned char... indexes >
constexpr unsigned long long mask(){
auto r = 0ull;
using discard_t = int[]; // data never used
// value never used:
discard_t discard = {0,(void(
r |= (1ull << indexes) // side effect, used
),0)...};
(void)discard; // block unused var warnings
return r;
}
または、もし c++11 に行き詰まったら、再帰的に解くことができる。
constexpr unsigned long long mask(){
return 0;
}
template<class...Tail>
constexpr unsigned long long mask(unsigned char b0, Tail...tail){
return (1ull<<b0) | mask(tail...);
}
template< unsigned char... indexes >
constexpr unsigned long long mask(){
return mask(indexes...);
}
3つ揃ったゴッドボルト -- CPP_VERSION の定義を変更することで、同じアセンブリを得ることができます。
実際には、私はできる限り最新のものを使用します。 14 は 11 に勝ります。なぜなら、再帰がなく、それゆえ O(n^2) シンボル長 (これはコンパイル時間とコンパイラのメモリ使用量を爆発的に増やす可能性があります) があるからです。
これらのうち、14は最も混乱させるものです。 ここでは、すべての0からなる無名配列を作成し、その間に副次的効果として結果を構築し、その後配列を破棄しています。 破棄された配列には、パックのサイズと同じ数の 0 と 1 (空のパックを処理できるように追加) が入っています。
がどのようなものかを詳しく説明します。 c++14 バージョンで行っていることを詳しく説明します。 これはトリック/ハックで、C++14 でパラメータ パックを効率的に展開するためにこれを行う必要があるという事実が、fold 式が c++17 .
内側から理解するのが一番です。
r |= (1ull << indexes) // side effect, used
これは単に
r
を
1<<indexes
は固定インデックス用です。
indexes
はパラメータパックなので、それを展開する必要があります。
残りの作業は、パラメータパックを用意して、拡張するための
indexes
の内部で展開するためのパラメータパックを提供することです。
一歩外に出る。
(void(
r |= (1ull << indexes) // side effect, used
),0)
ここで、式を
void
にキャストし、その戻り値を気にしないことを示します。
r
-- のような式は、C++では
a |= b
のような式は、設定した値も返します。
a
に設定した値も返します)。
次に、カンマ演算子
,
となり
0
を破棄して
void
を捨て、値
0
. つまり、この式は値が
0
を計算し、その副次的な効果として
0
のビットを設定します。
r
.
int discard[] = {0,(void(
r |= (1ull << indexes) // side effect, used
),0)...};
この時点で、パラメータパックを展開します。
indexes
. それで得られるのは
{
0,
(expression that sets a bit and returns 0),
(expression that sets a bit and returns 0),
[...]
(expression that sets a bit and returns 0),
}
の中に
{}
. この使用は
,
は
ではなく
ではなく、配列要素の区切り文字です。 これは
sizeof...(indexes)+1
0
のビットを設定することもできます。
r
のビットも設定します。 次に
{}
配列構築命令を配列
discard
.
次に
discard
を
void
-- ほとんどのコンパイラは、変数を作ってそれを読まなかった場合、警告を出します。 すべてのコンパイラはそれを
void
にキャストすれば、すべてのコンパイラは文句を言いませんが、これは一種の「はい、分かっています、私はこれを使いません"」と言う方法なので、警告は抑制されます。
関連
-
[解決済み] 非静的データメンバの無効な使用
-
[解決済み】システムが指定されたファイルを見つけられませんでした。
-
[解決済み] 解決済み] `pthread_create' への未定義の参照 [重複] [重複
-
[解決済み] 配列のベクトルを扱う正しい方法
-
[解決済み】変数やフィールドがvoid宣言されている
-
[解決済み】Eclipse IDEでC++エラー「nullptrはこのスコープで宣言されていません」が発生する件
-
[解決済み] 1ビットのセット、クリア、トグルはどのように行うのですか?
-
[解決済み] 32ビット整数のセットビットの数を数えるには?
-
[解決済み] C++で現在の時刻と日付を取得する方法は?
-
[解決済み】ビットシフト(bit-shift)演算子とは、どのようなもので、どのように機能するのですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】LLVMで暗黙のうちに削除されたコピーコンストラクタの呼び出し
-
[解決済み] エラーが発生する。ISO C++は型を持たない宣言を禁じています。
-
[解決済み】Cygwin Make bash コマンドが見つかりません。
-
[解決済み】テンプレートの引数1が無効です(Code::Blocks Win Vista) - テンプレートは使いません。
-
[解決済み】エラー:strcpyがこのスコープで宣言されていない
-
[解決済み】VC++の致命的なエラーLNK1168:書き込みのためにfilename.exeを開くことができません。
-
[解決済み】システムが指定されたファイルを見つけられませんでした。
-
[解決済み】警告 - 符号付き整数式と符号なし整数式の比較
-
[解決済み】'std::cout'への未定義の参照
-
[解決済み] C++のテンプレートはチューリングコンプリートか?