ボレロ村上 - ENiyGmaA Code

中3女子です。

小ネタ - 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 の基本方針に反してしまうだろう。


厄介である。