24
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-05-10

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

参考

24
14
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?