Edited at

if constexprを使うとき、特定条件時にコンパイルを失敗させる

More than 1 year has passed since last update.

C++は興味深い、でも難しい。タイトルの件について昨日悩み、詳しい人に相談し、解決したので、まとめました。

(2017/05/12追記) 同じ話題について、EzoeRyouさんの解説が出ています→ constexpr ifの落とし穴


注意書き

※ C++はお勉強中(数週間程度しか書いたことのない、本当にビギナーです)なので、用語の使い方や意味の説明について、正確/厳密ではない可能性が高いです。

もしどこか間違っていたらごめんなさい(できることなら、コメント欄などでご指摘いただけると嬉しいです)。


やりたいこと

C++17より導入されるはずのif constexpr文を使うと、テンプレートのインスタンス化時(コンパイル時)に、どの実装を生成するかを簡潔に書けるようになる。

template<class T>

int f() {
if constexpr (std::is_same<T, int>{}) {
// Tがintの時の実装
// ...
} else if constexpr (std::is_same<T, float>{}) {
// Tがfloatの時の実装
// ...
}
}

ここで、もしTintorfloatでない時はコンパイルを失敗させたいとする。つまり



  • Tに渡せる型は任意ではなく限られており、適切な型によってインスタンス化されていなかったなら、コンパイルエラーにしたい

  • コンパイルエラー時には、適切なエラーメッセージを出せると親切

これらの目標を満たす実装方法を調べた。


あまり良くなさそうな実装


上手くいかない

else節に、常に失敗するstatic_assertを置けば良いのではという発想で実装してみた。

一見するとなんとなく動きそうだが、上手くいかない。

template<class T>

int f() {
if constexpr (std::is_same<T, int>{}) {
// ...
} else if constexpr (std::is_same<T, float>{}) {
// ...
} else {
static_assert(false, "Bad T!!!!");
}
}

常にコンパイルエラーになってしまう。


動くが、やや冗長

if constexprを書き連ねる前にstatic_assertでガードする。

template<class T>

int f() {
static_assert(std::is_same<T, int>{} || std::is_same<T, float>{}, "Bad T!!!!")
if constexpr (std::is_same<T, int>{}) {
// ...
} else if constexpr (std::is_same<T, float>{}) {
// ...
}
}

この実装は期待通りの振る舞いをしてくれる。

ただしifの条件式がいくつもある場合はstatic_assertが膨れてしまうという問題がある。条件式の記述が実質的に2重になっている点も、DRYという観点から好ましくはない。


本筋っぽい実装

C++エキスパートに教わったやり方。まず次のようなエイリアスを定義しておく。

template <typename T>

constexpr bool false_v = false;

false_vを利用すると、fは次のように書ける。

template<class T>

int f() {
if constexpr (std::is_same<T, int>{}) {
// ...
} else if constexpr (std::is_same<T, float>{}) {
// ...
} else {
static_assert(false_v<T>, "Bad T!!!!");
}
}

意図的にTfalse_vに渡すことが重要らしい。

コンパイル例: https://wandbox.org/permlink/u7x2kxBmYN1zmYEE


解釈

上記実装で何が起きているかのおおざっぱな理解。


static_assertの評価タイミング

テンプレート引数に依存しないstatic_assertは、テンプレートのインスタンス化前にチェックされる。つまり static_assert(false) を関数内にそのまま書くのは実質的に

template<class T>

int f() {
static_assert(false);
}

こう書いてしまっているのと変わらない。もちろん常にstatic_assertで落ちてしまう。


static_assertの評価タイミングを期待通りにするには?

今回の例でいうと、static_assertの評価を、 テンプレートのインスタンス化後まで遅延させる と「そのブロックがインスタンス化された時にのみコンパイルエラーにする」が実現できる。

具体的なやり方としては、fのテンプレート引数Tが決まらないと評価されないような式を、static_assertに入れてやればよい(ので前述の実装になる)。false_vが使いもしないテンプレート引数を取るのは、依存関係を作るという意味がある。


まとめ

if constexpr文はあくまでテンプレートのインスタンス化をコントロールするものであって、ある部分のみコンパイル時に評価する類のものではないらしい。微妙なことをやろうとする場合には若干注意が必要そうだった。


参考