小ネタ - constexpr の文脈でconstメンバ関数と非constメンバ関数を呼び分ける
ちょっと嵌っていたのでメモがてら。
次のような get() メンバ関数を持つクラスと get() フリー関数があったとする。
template<typename T> struct X { T t; T& get() { /* 非const版 */ return t; } constexpr T const& get() const { /* const版 */ return t; } }; template<typename T> constexpr auto get(T&& t) -> decltype(t.get()) { /* 引数を T&& で受ける */ return t.get(); } int main() { constexpr int i = get( X<int>{1} ); }
引数を T&& で受けているのは、勿論 lvalue でも rvalue でも受け取れるようにするためだ。
しかし、残念ながらこのコードはコンパイルできない。
エラー: ‘constexpr decltype (t.get()) get(T&&) [with T = X<int>; decltype (t.get()) = int&]’ called in a constant expression 備考: ‘constexpr decltype (t.get()) get(T&&) [with T = X<int>; decltype (t.get()) = int&]’ is not usable as a constexpr function because: エラー: call to non-constexpr function ‘T& X<T>::get() [with T = int]’
なぜか。
rvalue を渡しているので、T&& は X
rvalue reference からオーバーロードされたメンバ関数を呼ぶと、非const版が呼び出される。
非constメンバ関数は constexpr でないので、非定数式になってしまう。
実行時処理ならまったく問題ないが、constexpr の文脈ではエラーになるのだ。
仮に元の get() フリー関数を書き換えたくない事情があったと仮定して、
X について rvalue を渡してもエラーにならないようにしてみよう。
template<typename T> constexpr auto get(X<T>&& t) -> decltype(static_cast<X<T> const&>(t).get()) { return const_cast<X<T> const&>(t).get(); }
単に rvalue reference についてconst版が呼び出されるようにオーバーロードしてやればよい。
それにしても、constexpr の文脈でも rvalue reference が常に非constになる仕様は面倒だと思う。
といって、例えば規格で constexpr の文脈では X const&& のような型に推論されるというような仕様にしたとすると、
「実行時とコンパイル時で全く同じ動作をする」という constexpr の基本方針に反してしまうだろう。
厄介である。