最終更新日時(UTC):
が更新

履歴 編集

constexpr関数が定数実行できない場合でも適格とする(C++23)

概要

いかなる呼び出しにおいても定数式実行できないconstexpr関数が存在しても、プログラムが不適格にならないようにする。

ただし、このような関数の存在までは許容するというだけで、定数式実行できないconstexpr関数を定数式文脈で呼び出すと従来通りエラーとなる。

この機能が必要になった背景・経緯

これまでは、いかなる実引数での呼び出しでも定数式実行できないconstexpr関数が存在するだけでプログラムは不適格になっていた。

例えば以下のような場合である。

int f(int x) { return x + 1; }

constexpr int g(int x) { return f(x); } // error! fはいかなるxについても定数式実行不可能

gconstexprでない関数fを呼び出すため、プログラムの中で実際にgが定数式実行されない場合であっても、このコードは不適格である。 これによって、定数式実行できない言語機能を使用している関数や、constexprでない関数を誤って呼び出すようなミスを検出することができた。

しかし、多くの標準ライブラリがconstexpr対応を進めていくようになり、状況が変化した。

例えば、std::optionalresetメンバメソッドがconstexprに対応するのはC++23以降である。 これはstd::optionalの内部実装がunionのアクティブメンバを更新しているからであり、これを定数式内で実行するにはC++20を待たねばならなかった(参考:定数式内での共用体のアクティブメンバの変更を許可)。

よって、以下のコードはC++17では不適格だが、C++23では正しいコードとなる。

template<typename T>
constexpr void f(std::optional<T>& opt)
{
  opt.reset();
}

多くのバージョンで使われることが想定されるライブラリにおいてこれを最大限活用できるよう記述するには、constexpr指定するかどうかをマクロで変更しなければならない。 例えばC++23からconstexprになる関数群に対して以下のようなマクロを使うか、

#if __cplusplus >= 202300L // 具体的な値は未定
#  define MYLIB_CXX23_CONSTEXPR constexpr
#else
#  define MYLIB_CXX23_CONSTEXPR
#endif

template<typename T>
MYLIB_CXX23_CONSTEXPR void f(std::optional<T>& opt)
{
  opt.reset();
}

あるいはより細かく指定するためにそれぞれの機能テストマクロを使うしかなかった。

template<typename T>
#if __cpp_lib_optional >= 202106
constexpr
#endif
void f(std::optional<T>& opt)
{
  opt.reset();
}

これはいかにも冗長であり、またライブラリの導入時期だけでなくその関数一つ一つがconstexpr指定されたタイミングを意識する必要があるため、正しく指定するのは非常に難しい。

constexpr指定を行わないことによってもこの問題は回避できるが、自身以外のユーザーが存在するライブラリの場合はconstexpr対応が望まれる可能性があり、その場合はこの問題に対処する必要が生じる。

多くの場合、標準ライブラリ関数がconstexpr指定されるのは、その機能が定数式実行可能になったバージョンよりも遅れる。 例えば、std::arrayoperator[]の非const版がconstexpr指定されるのは、定数式内で変数の変更が許可されるC++14ではなく、その次のC++17である(参考)。 このような状況では上記のような解決策を用いてもミスを避けることは容易ではない。

現在、登場時点では定数式内で実行できなかったためにconstexprされていなかった多くの標準ライブラリ関数が、のちにコア言語機能が追加されてconstexpr指定されている。 そのため、現時点でconstexprではない関数も次のバージョンでconstexprになるかもしれず、よって関数がconstexpr指定されているだけでエラーにする意義は薄れてきた。

以上から、コンパイル時に実行できないconstexprをエラーとするのは、実際にコンパイル時に呼びだされてからでよい、ということになった。

実際に定数式内で実行できない関数が定数式内で呼び出された場合、これは従来通りエラーとするほかない。 しかし、定数式内で呼び出されていないのならば、定数式内で実行できない関数が存在していてもプログラムを不適格とはしない。

仕様

constexpr指定された関数が満たすべき条件を緩和する。

  • 関数の返り値はliteral型でなくともよい。
  • 関数の実引数はどれもliteral型でなくともよい。
  • いかなる実引数が与えられても定数式実行不可能でもよい
  • いかなるtemplate実引数が与えられても定数式実行不可能でもよい

constexpr指定された、=delete指定されていないコンストラクタが満たすべき条件を緩和する。

  • 非静的メンバ変数のコンストラクタはconstexprでなくともよい
  • 委譲コンストラクタの場合、委譲先のコンストラクタがconstexprでなくともよい

constexpr指定された、=delete指定されていないデストラクタが満たすべき条件を緩和する。

  • 非静的メンバ変数のデストラクタはconstexprでなくともよい

また、陽にdefault指定された関数は、それがconstexpr-suitableである限り、暗黙にconstexpr指定される。 constexpr-suitableとは、コルーチン関数ではなく、仮想基底クラスを持つクラスのコンストラクタまたはデストラクタでもない関数を指す。

関連項目

参照