ついに実現！実用的なC++20コンパイル時出力

# はじめに

早いもので、今年ももう**大晦日**です。
大晦日といえば、やることは1つです。

そう、<font color="red">**コンパイル時処理**</font>ですね！！

## コンパイル時出力

C++ のコンパイル時処理は非常に強力で、様々なことがコンパイル時にできます。
入力に依存しない計算なら、大抵コンパイル時にしてしまうことができます。

しかし、その結果の出力については実行時に行う必要があり、本当にコンパイル時に処理できているのか分かりにくくなってしまうこともあります。

そこで、なんかこういろいろ頑張ってゴリ押すことで、制限はありますが**コンパイル時に出力**することができます。

https://qiita.com/Raclamusi/items/418d08f8e64fa1959850

[先日公開した記事](https://qiita.com/Raclamusi/items/418d08f8e64fa1959850)では、そんなコンパイル時出力について書いています。

## コンパイル時出力の改良

この記事の目的は、**コンパイル時出力の改良**です。

現在のコンパイル時出力は次のような問題点があり、使いやすくはありません。

- 出力の前後に**余計な出力**（はみ出たエラーメッセージ）がある
- ターミナルのサイズや出力の長さによって、**手動で調整**しなければならないパラメータがある
- エラーメッセージを利用するため**コンパイルエラー**となる

![result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643544/9244375f-e8f8-1922-d02a-356d409956e4.png)

この記事では、これらの問題を解決することを目的とします。

# アセンブル時出力

結果からいうと、コンパイル時に解決するのは無理です！

「え、じゃあどうするんですか？」

**コンパイル時出力を諦めます！**

「え？」

<font color="red">**アセンブル時に出力**</font>します！

# GNU アセンブラの .print 命令

GNU アセンブラには、アセンブル時に文字列を出力する擬似命令、 **`.print`** があります[^aimai]。
（ `.print` 命令については[前回の記事のコメント](https://qiita.com/Raclamusi/items/418d08f8e64fa1959850#comment-ba5e07fb52414e5d02de)で @fujitanozomu さんに教えていただきました。）

[^aimai]: ~~どの形式のアセンブリ言語の機能なのかとか `.print` が何であるかとかがすごく曖昧ですが、調べても出てこなかったのでそのままにしました。知ってたら教えてください。~~ 修正しました。

インラインアセンブラでこれを使うと、**アセンブル時に文字列を出力**できます。
ついでに、 GCC なら `\` によるエスケープも処理してくれます。

```c++:asm_print.cpp
int main() {
    asm(R"(
        .print "Hello,\nWorld!"
    )");
}
```

```console
$ g++ asm_print.cpp
Hello,
World!
```

上のコードをコンパイルすると、コンパイル時に出力が行われているように見えます。
これは**広義のコンパイル出力**といってもいいのではないでしょうか！

この出力が行われるのはアセンブル時、つまり**コンパイルの後**です。
つまり、**コンパイル時処理の結果を反映**した出力ができるということです！

さらに、 `static_assert` を利用する場合と違い、**指定したメッセージ以外の出力が一切ありません**！
とてもスッキリした出力となっています！

さらにさらに、 `static_assert` とは違い、**コンパイルが通ります**！
実行ファイルを生成しつつ文字列を出力することができ、用途がかなり広がりそうです！

（え、都合よすぎないですか？）

# インラインアセンブラの問題点

まあ、そんなすべてが都合いいなんてことはなく、問題点もあります。
それは、 C++20 では**インラインアセンブラが constexpr 関数内で評価できない**ことです。

これでは少し不便です。

```c++:asm_print2.cpp
constexpr void f() {
    asm(".print \"Hey!\"");
}
int main() {
    f();
}
```

```console
$ g++ -std=c++20 asm_print2.cpp
asm_print2.cpp: In function 'constexpr void f()':
asm_print2.cpp:2:5: error: inline assembly is not a constant expression [-Winvalid-constexpr]
    2 |     asm(".print \"Hey!\"");
      |     ^~~
asm_print2.cpp:2:5: note: only unevaluated inline assembly is allowed in a 'constexpr' function in C++20
```

……おや？

# 未評価のインラインアセンブリはOK

`only unevaluated inline assembly is allowed in a 'constexpr' function in C++20` という note が出ていますね。
ということは、 unevaluated ならいいのでしょうか？

そう、実は C++20 から constexpr 関数内でも**未評価であれば `asm` 宣言を使うことが許可**されるようになりました。

https://cpprefjp.github.io/lang/cpp20/enabling_constexpr_intrinsics_by_permitting_unevaluated_inline-assembly_in_constexpr_functions.html

（え、本当に都合よすぎないですか？）

`.print` は実行時ではなくアセンブル時に出力するためのもので、アセンブリコードが生成されさえすれば出力されるので、評価される必要はありません。

ならば、後は**アセンブリコードを生成しつつ評価しない**ようなコードが書ければよいです。
さて、そんなコードどう書けばいいのでしょうか。

# 不思議な std::is_constant_evaluated

アセンブリコードを生成しつつ評価しないコードを書くため、いろいろ試してみましょう。

評価しないようにするなら `if (false)` に入れればいいのでは？

```c++:asm_print3.cpp
constexpr void f() {
    if (false) asm(".print \"Hey!\"");
}
int main() {
    f();
}
```

```console
$ g++ -std=c++20 asm_print3.cpp

```

消滅してしまいました……。

変数を挟めばどうでしょう？

```c++:asm_print4.cpp
constexpr void f() {
    bool b = false;
    if (b) asm(".print \"Hey!\"");
}
int main() {
    f();
}
```

```console
$ g++ -std=c++20 asm_print4.cpp
Hey!
```

お！

```console
$ g++ -std=c++20 -O2 asm_print4.cpp

```

あぁ、最適化で消滅してしまいました……。

ならば `volatile` を付ければ……！

```c++:asm_print5.cpp
constexpr void f() {
    volatile bool b = false;
    if (b) asm(".print \"Hey!\"");
}
int main() {
    f();
}
```

```console
$ g++ -std=c++20 -O2 asm_print5.cpp
asm_print5.cpp: In function 'constexpr void f()':
asm_print5.cpp:3:9: error: lvalue-to-rvalue conversion of a volatile lvalue 'b' with type 'volatile bool' [-Winvalid-constexpr]
    3 |     if (b) asm(".print \"Hey!\"");
      |         ^
```

なんか怒られてしまいました……。

うーん、なにかこう、常に false に評価されるけど if 文の中身を消さないような式はないのでしょうか。

ありました。 <code>not <b>std::<a href="https://cpprefjp.github.io/reference/type_traits/is_constant_evaluated.html">is_constant_evaluated</a>()</b></code> です[^hokanimo]。
これはコンパイル時は常に false に評価されますが、実行時は true になるため if 文の中身が消されることはありません。

[^hokanimo]: ほかにもありそう。

```c++:asm_print6.cpp
#include <type_traits>
constexpr void f() {
    if (not std::is_constant_evaluated()) asm(".print \"Hey!\"");
}
int main() {
    f();
}
```

```console
$ g++ -std=c++20 -O2 asm_print6.cpp
Hey!
```

やったね！

# コンパイル時出力ライブラリ constexpr_print

これで準備は整いました！
[前回](https://qiita.com/Raclamusi/items/418d08f8e64fa1959850)作った[コンパイル時出力のプログラム](https://gist.github.com/Raclamusi/b365019f7605192162d5e35ffaed0a54)を書き換えて、使いやすくします。

ざっくりいうと、 <code>static_assert(<i>condition</i>, <i>message</i>);</code> としていたところを <code>if constexpr (not <i>condition</i>) { if (not std::is_constant_evaluated()) asm(".print \\"" <i>escaped-message</i> "\\""); }</code> に置換します。
エスケープシーケンスによるカーソル操作は、エラーメッセージが出力された行を遡る分が不要になり、上下方向は1行上に移動するだけでよくなります。

**そしてできたものがこちらです！**

https://github.com/Raclamusi/constexpr_print

```c++:sample.cpp
#include "constexpr_print.hpp"

#include <iostream>
#include <array>
#include <string_view>
#include <algorithm>
#include <concepts>

template <std::unsigned_integral T>
constexpr std::size_t digits(T x) {
    std::size_t d = 1;
    while (x /= 10) ++d;
    return d;
}

template <std::size_t N>
struct fizzbuzz {
    static constexpr std::size_t size =
        fizzbuzz<N - 1>::size + (
            N % 15 == 0 ? 9 :
            N % 3 == 0 || N % 5 == 0 ? 5 :
            digits(N) + 1
        );
    static constexpr std::array<char, size> data = [] {
        using namespace std::literals::string_view_literals;
        std::array<char, size> data;
        auto it = std::ranges::copy(fizzbuzz<N - 1>::data, data.begin()).out;
        if (N % 15 == 0) std::ranges::copy("FizzBuzz\n"sv, it);
        else if (N % 3 == 0) std::ranges::copy("Fizz\n"sv, it);
        else if (N % 5 == 0) std::ranges::copy("Buzz\n"sv, it);
        else {
            auto it = data.rbegin();
            *it++ = '\n';
            auto n = N;
            do *it++ = '0' + n % 10;
            while (n /= 10);
        }
        return data;
    }();
    static constexpr std::string_view value{ data.data(), size };
};

template <>
struct fizzbuzz<0> {
    static constexpr std::size_t size = 0;
    static constexpr std::array<char, size> data {};
    static constexpr std::string_view value{ data.data(), size };
};

int main() {
    constexpr_print("It's compile time!");
    constexpr_print(fizzbuzz<100>::value);

    std::cout << "It's runtime!\n";
    std::cout << fizzbuzz<100>::value;
}
```

```console
$ g++ -std=c++20 sample.cpp -o sample
It's compile time!
1
2
Fizz
4
Buzz
︙

$ ./sample
It's runtime!
1
2
Fizz
4
Buzz
︙

$ 
```

始めに目的とした問題が解決され、**余計な出力がなく**なり、**手動でのパラメータ調整が不要**になり、**実行時処理との共存**もできています。
**見やすく、実用的なコンパイル時出力ができました！**

# おわりに

前回かなりのゴリ押しと妥協でコンパイル時出力を実現しましたが、今回の改良で実用的なものとなり、普段使いしやすくなったと思います。
なので、皆さん、普段使いしましょう。

**皆でコンパイル時処理をしましょう。**

---

**謝辞**
アセンブリ言語の `.print` についてコメントして頂いた @fujitanozomu 氏に感謝します。