includeディレクティブは3種類あった

お久しぶりの初心者Cプログラマーです。いつから初心者を脱するのか不明ですが初心者です。今回ちょっとした小ネタ投稿。

# `#include`ディレクティブ? 2種類でしょ？

Cプログラマーであればおよそ誰もが使う[^1]`#include`。概ね以下の2つであろうと思います。

```c:includeは2種類
// <>で囲むやつ
#include <stdio.h>

// ""で囲むやつ
#include "private_use.h"
```

はい、普通これでOKですが、第3の形式があります。

# マクロ展開によるパターン増

それがこれです。

```c:includeパターン3
#define HEADER_MACRO "switched.h"
#include HEADER_MACRO
```

すべてプリプロセス時点で完結するからできるのかもしれませんが、こんな~~誰が使うのか分からない~~指定ができるわけです。

# どうやって使うのか？

具体的に何ができるかというと、以下のようなことができます。

```c:include_ver1.h
#ifndef INCLUDE_VER1_H_INCLUDE
#define INCLUDE_VER1_H_INCLUDE

#include <stdio.h>

#define MSG_FUNC(fmt, ...) printf("LOG ver1: " fmt "\n", ##__VA_ARGS__)

#endif
```

```c:include_ver2.h
#ifndef INCLUDE_VER2_H_INCLUDE
#define INCLUDE_VER2_H_INCLUDE

#include <stdio.h>

#define MSG_FUNC(fmt, ...) printf("LOG ver2: " fmt "\n", ##__VA_ARGS__)

#endif
```

```c:include_test.c
#ifdef USE_VER1
    #define INCLUDE_FILE "include_ver1.h"
#else
    #define INCLUDE_FILE "include_ver2.h"
#endif

#include INCLUDE_FILE

int main(void)
{
    MSG_FUNC("use header %s", INCLUDE_FILE);
}
```

見れば分かるかと思いますが、`USE_VER1`が定義されていれば`include_ver1.h`を、そうでなければ`include_ver2.h`をインクルードしようとしています。これが機能すれば、`USE_VER1`定義ありの場合は`MSG_FUNC`で"LOG ver1: "が、定義なしなら"LOG ver2: "が出力されるはずです。

手元のWSL2/ubuntu20.04環境で実行したところ、以下のような実行結果が得られました。

```text:実行結果
$ gcc --version
gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ clang --version
clang version 10.0.0-4ubuntu1 
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
$ gcc -Wall -Wextra include_test.c 
$ ./a.out 
LOG ver2: use header include_ver2.h
$ gcc -Wall -Wextra -D USE_VER1 include_test.c 
$ ./a.out 
LOG ver1: use header include_ver1.h
$ clang -Wall -Wextra include_test.c 
$ ./a.out 
LOG ver2: use header include_ver2.h
$ clang -Wall -Wextra -D USE_VER1 include_test.c 
$ ./a.out 
LOG ver1: use header include_ver1.h
```

適用されていそうです。

# それC言語じゃなくてプリプロセッサが気を利かせただけじゃないの？

と我に返る方もいるでしょうから、規格を見ましょう。いつものn1570(C11のdraft)[^2]です。

> 6.10.2-4
> A preprocessing directive of the form
>   `# include pp-tokens new-line`
> (that does not match one of the two previous forms) is permitted. The preprocessing tokens after include in the directive are processed just as in normal text. (Each identifier currently defined as a macro name is replaced by its replacement list of preprocessing tokens.) The directive resulting after all replacements shall match one of the two previous forms. ...

拙訳：
`# include プリプロセストークン 改行`
この形式の(前の2形式のいずれにも該当しない)プリプロセス命令が許容される。命令内で`include`の後に置かれるプリプロセストークンは、通常のテキストのようにプロセスされる。(それぞれ今マクロ名として定義されている識別子はプリプロセストークンのリストで置換される。)置換の結果最終的に生成された命令は前の2形式のいずれかに合致せねばならない。(後略)

これを見る限り、C言語仕様の範疇ですね。実際、6.10.2-8に例まで載せて説明してくれています。

```c:規格記載の例
#if VERSION == 1
#define INCFILE "vers1.h"
#elif VERSION == 2
#define INCFILE "vers2.h" // and so on
#else
#define INCFILE "versN.h"
#endif
#include INCFILE
```

ちなみに、n4659(C++17のdraft)[^3]ではどうかというと、19.2-4にほぼ同様の内容があります。

# 最後に

~~ぶっちゃけ使う機会がないですが、~~ 久々に調べていて楽しかったです。

[^1]: [古のC](https://qiita.com/cat-in-136/items/6458ff7e7fd11e4ded42)なら標準の関数その他を`extern`してた、とかの突っ込みはナシで……。
[^2]: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
[^3]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf