Cプリプロセッサの話盛り合わせ

-- Views

December 20, 25

スライド概要

profile-image

きりんさんがすきです。でもC++さんのほうがもーっとすきです。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

ダウンロード

関連スライド

各ページのテキスト
1.

Cプリプロセッサの話 盛り合わせ �/��

2.

�. #elifdef �/��

3.

�. #elifdef • C++��から追加された⾮常に⾼度で先進的な複合機能 • #elif + #ifdef �/��

4.

�. #elifdef • C++��から追加された⾮常に⾼度で先進的な複合機能 • #elif + #ifdef • 例: #ifdef ENABLE_HOGE # include "hoge.hpp" #elifdef ENABLE_FUGA // cf. #elif defined(ENABLE_FUGA) # include "fuga.hpp" #endif �/��

5.

�. #elifndef �/��

6.

�. #elifndef • C++��から追加された⾮常に⾼度で先進的な複合機能 その2 • #elif + #ifndef �/��

7.

�. #elifndef • C++��から追加された⾮常に⾼度で先進的な複合機能 その2 • #elif + #ifndef • 例: 省略 �/��

8.

3. そのdefined、不要では? �/��

9.

3. そのdefined、不要では? #if defined ENABLE_HOGE # if ENABLE_HOGE # include "hoge.hpp" # endif #endif 例: あるオプション機能の有効・無効を制御するためのマクロ ENABLE_HOGE で分岐する • ENABLE_HOGE が #define されていない → 無効 • 0 以外の整数として #define されている → 有効 • 空、もしくは数値と解釈できないトークンとして #define され ている → コンパイルエラー �/��

10.

3. そのdefined、不要では? #if ENABLE_HOGE # include "hoge.hpp" #endif ↑これでよい ��/��

11.

3. そのdefined、不要では? #if ENABLE_HOGE # include "hoge.hpp" #endif ↑これでよい • #if には「最後まで展開した結果、 #define されていない識別 ⼦が現われたら0として扱う」という仕様がある。 • なので、「 #define されている、かつ0でない」やその否定 「 #define されていない、または0である」の場合は単に ENABLE_HOGE (否定の場合は !ENABLE_HOGE ) とすればよい。 ��/��

12.

�. #embed ��/��

13.

�. #embed // a.cpp char const data[] = { # embed "fox.txt" }; // fox.txt the quick brown fox ファイルの内容をコンマ区切りの整数トークン列として取り込 む機能 ��/��

14.

�. #embed // a.cpp char const data[] = { 116, 104, 101, 32, 113, … //'t', 'h', 'e', SP, 'q', }; ⼤体こういう感じになる。(テキストファイルでなくとも可) ��/��

15.
[beta]
�. #embed
先の例で作った data を std::print で表⽰する。
std::println("{}", data);

結果
Segmentation Fault

��/��

16.

YOU DIED ��/��

17.

�. embed Q. なぜ? A. dataはヌル終端でない。 ��/��

18.
[beta]
�. #embed
Q. どうするの?
A. #embed にオプションを追加する。
char const data[] = {
# embed "fox.txt" suffix(,)
'\0'
};

suffix(TOKENS) は、埋め込むデータが空でない場合、データ末尾

に TOKENS を置く。(例では , が置かれる)

��/��

19.

�. #embed その他のオプション • prefix(TOKENS) : 埋め込むデータが空でない場合、データ先頭 に TOKENS を置く。 • limit(N) : Nバイトだけ埋め込む。 • if_empty(TOKENS) : 埋め込むデータが空だった場合、代わりに TOKENS を置く。 • offset(N) : データ先頭からNバイトスキップする。 関連する機能 • __has_embed(NAME ARGS) : __has_include(NAME) の親戚。 #embed NAME ARGS が無効なとき、空データのとき、空でない データのときを識別できる。 ��/��

20.

�. #embed CPP外の類似機能 • GIMP (GNU Image Manipulation Program)、その他ドット 絵エディタ: さっきの例のようなC⾔語の配列変数として画像を出力する 機能がある。 • std::embed() C++��での採⽤に向けて策定中。プリプロセスではなくコン パイラマジック関数として提供される。 でかいファイルを埋め込むにはこのほうが速くコンパイルで きるらしい。 https://www.open-std.org/jtc�/sc��/wg��/docs/ papers/����/p����r�.html • incbin (GNU as) 次ページ ��/��

21.
[beta]
�. #embed の類似機能: incbin
アセンブリ⾔語でオブジェクトファイルにデータを埋め込む機
能。従来から使われている。
__asm__ (R"(
data:
.incbin "fox.txt"
.byte 0
data_size: .8byte . - data - 1
)");
extern char const data[];
extern std::size_t data_size;
std::print("size: {}\ndata: {}", data_size, (char const *)data);

出力
size: 20
data: the quick brown fox

��/��

22.

�. __VA_OPT__() ��/��

23.

�. __VA_OPT__() • C++��で導⼊された __VA_ARGS__ に関する補助的な機能 • __VA_OPT__(x) は x に展開されるが、 __VA_ARGS__ が空の場合は同 じく空となる。 • 主な⽤途は関数やマクロの引数リストに __VA_ARGS__ を追加す る際、 __VA_ARGS__ が空のときだけカンマを消すこと。 ��/��

24.
[beta]
�. __VA_OPT__()
例: debug printでsource_locationを追加する
// a.cpp
#define DPRINT(fmt, ...) \
std::println(stderr, "{}: " fmt, \
std::source_location::current() __VA_OPT__(,) \
__VA_ARGS__ \
)
DPRINT("passed"); // a.cpp:9: passed
DPRINT("x={}", 42); // a.cpp:10: x=42

注意: この例と同じことはマクロを使わずともできる

��/��

25.

�. __VA_OPT__() 応⽤1: トークン列が空か判定する #define PP_IS_EMPTY(...) PP_IS_EMPTY_I(__VA_ARGS__) #define PP_IS_EMPTY_I(...) PP_IS_EMPTY_II(__VA_OPT__(0,) 1) #define PP_IS_EMPTY_II(x, ...) x ��/��

26.

�. __VA_OPT__() 応⽤2: 引数リストを数える #define PP_SIZE(...) PP_SIZE_I(__VA_ARGS__) #define PP_SIZE_I(...) PP_SIZE_II(__VA_ARGS__ __VA_OPT__(,) 4, 3, 2, 1, 0 #define PP_SIZE_II(_4, _3, _2, _1, _0, ...) _0 PP_SIZE() // 0 PP_SIZE(a) // 1 PP_SIZE(a, b) // 2 PP_SIZE(a, , c) // 3 ��/��

27.

�. __VA_OPT__() 性質 x , y をカッコ平衡なトークン列として、 • 分配法則: __VA_OPT__(x) __VA_OPT__(y) = __VA_OPT__(x y) • 冪等性: ◦ __VA_ARGS__ が空のときに x も空であれば、 x = __VA_OPT__(x) ▪ 特に、 __VA_ARGS__ = __VA_OPT__(__VA_ARGS__) ◦ __VA_OPT__(x) = __VA_OPT__(__VA_OPT__(x)) ��/��

28.

�. __VA_OPT__() 応⽤3: map f と a, b, c, … と k について f(k, a), f(k, b), f(k, c), … と展開す るマクロ #define PP_MAP(f, k, ...) __VA_OPT__(PP_MAP_I(f, k, __VA_ARGS__))) #define PP_MAP_I(f, k, ...) __VA_OPT__(PP_MAP_0(f, k, __VA_ARGS__)) #define PP_MAP_0(f, k, x, ...) \ f(k, x) __VA_OPT__(PP_MAP_1(f, k, __VA_ARGS__)) #define PP_MAP_1(f, k, x, ...) \ f(k, x) __VA_OPT__(PP_MAP_2(f, k, __VA_ARGS__)) #define PP_MAP_2(f, k, x, ...) \ f(k, x) __VA_OPT__(PP_MAP_3(f, k, __VA_ARGS__)) #define PP_MAP_3(f, k, x, ...) \ f(k, x) __VA_OPT__(PP_MAP_ERROR_ITERATE_OVER) #define F(k, x) f(k, x); PP_MAP(F, 42, 1, 2, 3) // f(42, 1); f(42, 2); f(42, 3); ��/��

29.

6. 連番マクロを使わずに反復する ��/��

30.

6. 連番マクロを使わずに反復する 先の例のように、マクロデータ構造を反復処理するには同じ内 容の連番コピペマクロを作るのが定番。 // 再掲 #define PP_MAP(f, k, ...) __VA_OPT__(PP_MAP_I(f, k, __VA_ARGS__))) #define PP_MAP_I(f, k, ...) __VA_OPT__(PP_MAP_0(f, k, __VA_ARGS__)) #define PP_MAP_0(f, k, x, ...) \ f(k, x) __VA_OPT__(PP_MAP_1(f, k, __VA_ARGS__)) #define PP_MAP_1(f, k, x, ...) \ f(k, x) __VA_OPT__(PP_MAP_2(f, k, __VA_ARGS__)) #define PP_MAP_2(f, k, x, ...) \ f(k, x) __VA_OPT__(PP_MAP_3(f, k, __VA_ARGS__)) #define PP_MAP_3(f, k, x, ...) \ f(k, x) __VA_OPT__(PP_MAP_ERROR_ITERATE_OVER) ��/��

31.

6. 連番マクロを使わずに反復する これが64個とか256個とか並んでいると⾮常に冥界で鬱くしい コードになりがち。 • 魔クロ • 魔黒 • 真っ黒 ��/��

32.

6. 連番マクロを使わずに反復する 界隈では知られている技法 (名前が分からない) 例: 列挙型と⽂字列変換関数を⽣成するマクロ DEF_ENUM #define COLOR_LIST(def, x) \ def(x, red, 1) /* 値の指定はオプション */ \ def(x, green) \ def(x, blue) DEF_ENUM(color, COLOR_LIST); ��/��

33.
[beta]
6. 連番マクロを使わずに反復する
界隈では知られている技法 (名前が分からない)
例: 列挙型と⽂字列変換関数を⽣成するマクロ DEF_ENUM
// こんな感じになる
enum struct color { red = 1, green, blue };
std::string name_of(color _v_) {
switch (_v_) {
case color::red: return "color::red";
case color::green: return "color::green";
case color::blue: return "color::blue";
default: throw std::invalid_argument{};
}
}

��/��

34.
[beta]
6. 連番マクロを使わずに反復する
実装
#define DEF_ENUM(tname, list) \
enum struct tname { list(DEF_ENUM_MEMBER, ) }; \
std::string name_of(tname _v_) { \
switch (_v_) { \
list(DEF_ENUM_CASE, tname) \
default: throw std::invalid_argument{}; \
} \
}
#define DEF_ENUM_MEMBER(_, e, ...) \
e __VA_OPT__(= (__VA_ARGS__)),
#define DEF_ENUM_CASE(tname, e, ...) \
case tname::e: return #tname "::" #e;

簡単!*
* 個⼈の感想です。体感には個⼈差があります。

��/��

35.

おわり ��/��